use askama::Template; use axum::{ extract::{Path, State}, response::{Html, IntoResponse as _, Response}, }; use phono_backends::{pg_acl::PgPrivilegeType, pg_attribute::PgAttribute}; use phono_models::{ accessors::{Accessor, Actor, portal::PortalAccessor}, expression::PgExpressionAny, workspace::Workspace, }; use serde::{Deserialize, Serialize}; use sqlx::postgres::types::Oid; use uuid::Uuid; use crate::{ app::AppDbConn, errors::AppError, navigator::Navigator, settings::Settings, user::CurrentUser, workspace_nav::{NavLocation, RelLocation, WorkspaceNav}, workspace_pooler::{RoleAssignment, WorkspacePooler}, }; #[derive(Debug, Deserialize)] pub(super) struct PathParams { portal_id: Uuid, rel_oid: u32, workspace_id: Uuid, } /// HTTP GET handler for the table viewer page of a [`Portal`]. This handler /// performs some relatively simple queries pertaining to table structure, but /// the bulk of the query logic resides in the [`super::get_data_handler`] /// module. pub(super) async fn get( State(settings): State, State(mut pooler): State, AppDbConn(mut app_db): AppDbConn, CurrentUser(user): CurrentUser, navigator: Navigator, Path(PathParams { portal_id, rel_oid, workspace_id, }): Path, ) -> Result { let mut workspace_client = pooler .acquire_for(workspace_id, RoleAssignment::User(user.id)) .await?; let portal = PortalAccessor::new() .id(portal_id) .as_actor(Actor::User(user.id)) .verify_workspace_id(workspace_id) .verify_rel_oid(Oid(rel_oid)) .verify_rel_permissions([PgPrivilegeType::Select]) .using_app_db(&mut app_db) .using_workspace_client(&mut workspace_client) .fetch_one() .await?; let attrs = PgAttribute::all_for_rel(portal.class_oid) .fetch_all(&mut workspace_client) .await?; let attr_names: Vec = attrs.iter().map(|attr| attr.attname.clone()).collect(); #[derive(Clone, Debug, Serialize)] struct ColumnInfo { name: String, regtype: String, } let columns: Vec = attrs .iter() .map(|attr| ColumnInfo { name: attr.attname.clone(), regtype: attr.regtype.clone(), }) .collect(); let workspace = Workspace::with_id(portal.workspace_id) .fetch_one(&mut app_db) .await?; #[derive(Template)] #[template(path = "portal_table.html")] struct ResponseTemplate { columns: Vec, attr_names: Vec, filter: Option, settings: Settings, navbar: WorkspaceNav, } Ok(Html( ResponseTemplate { columns, attr_names, filter: portal.table_filter.0, navbar: WorkspaceNav::builder() .navigator(navigator) .workspace(workspace) .populate_rels(&mut app_db, &mut workspace_client) .await? .current(NavLocation::Rel( portal.class_oid, Some(RelLocation::Portal(portal.id)), )) .build()?, settings, } .render()?, ) .into_response()) }