use anyhow::Result; use askama::Template; use derive_builder::Builder; use interim_models::{client::AppDbClient, portal::Portal, workspace::Workspace}; use interim_pgtypes::{ client::WorkspaceClient, pg_class::{PgClass, PgRelKind}, }; use sqlx::postgres::types::Oid; use uuid::Uuid; use crate::navigator::Navigator; #[derive(Builder, Clone, Debug, Template)] #[template(path = "workspace_nav.html")] pub(crate) struct WorkspaceNav { workspace: Workspace, relations: Vec, #[builder(default, setter(strip_option))] current: Option, navigator: Navigator, } impl WorkspaceNav { pub fn builder() -> WorkspaceNavBuilder { WorkspaceNavBuilder::default() } } #[derive(Clone, Debug)] pub struct RelationItem { pub name: String, pub oid: Oid, pub portals: Vec, } #[derive(Clone, Debug)] pub struct PortalItem { pub name: String, pub id: Uuid, } #[derive(Clone, Debug, PartialEq)] pub enum NavLocation { Rel(Oid, Option), } #[derive(Clone, Debug, PartialEq)] pub enum RelLocation { Portal(Uuid), Sharing, } impl WorkspaceNavBuilder { /// Helper function to populate relations and lenses automatically. /// [`WorkspaceNavBuilder::workspace()`] must be called first, or else this /// method will return an error. pub async fn populate_rels( &mut self, app_db: &mut AppDbClient, workspace_client: &mut WorkspaceClient, ) -> Result<&mut Self> { let rels = PgClass::with_kind_in([PgRelKind::OrdinaryTable]) .fetch_all(workspace_client) .await?; let mut rel_items = Vec::with_capacity(rels.len()); for rel in rels { if rel.regnamespace.as_str() != "pg_catalog" && rel.regnamespace.as_str() != "information_schema" { let portals = Portal::belonging_to_workspace( self.workspace .as_ref() .ok_or(WorkspaceNavBuilderError::UninitializedField("workspace"))? .id, ) .belonging_to_rel(rel.oid) .fetch_all(app_db) .await?; rel_items.push(RelationItem { name: rel.relname, oid: rel.oid, portals: portals .into_iter() .map(|portal| PortalItem { name: portal.name, id: portal.id, }) .collect(), }); } } Ok(self.relations(rel_items)) } }