use std::collections::HashMap; use askama::Template; use axum::{ extract::{Path, State}, response::{Html, IntoResponse as _, Response}, }; use interim_models::{ field::Field, field_form_prompt::FieldFormPrompt, language::Language, portal::Portal, presentation::{Presentation, TextInputMode}, }; use interim_pgtypes::{pg_attribute::PgAttribute, pg_class::PgClass}; use serde::Deserialize; use uuid::Uuid; use crate::{ Settings, app::AppDbConn, errors::{AppError, not_found}, field_info::FormFieldInfo, workspace_pooler::{RoleAssignment, WorkspacePooler}, }; #[derive(Debug, Deserialize)] pub(super) struct PathParams { portal_id: Uuid, } pub(super) async fn get( State(settings): State, State(mut pooler): State, AppDbConn(mut app_db): AppDbConn, Path(PathParams { portal_id }): Path, ) -> Result { let portal = Portal::with_id(portal_id) .fetch_optional(&mut app_db) .await? .ok_or(not_found!("form not found"))?; // FIXME: auth // WARNING: This client is connected with full workspace privileges. Even // more so than usual, the Phonograph server is responsible for ensuring all // auth checks are performed properly. // // TODO: Can this be delegated to a dedicated and less privileged role // instead? let mut workspace_client = pooler .acquire_for(portal.workspace_id, RoleAssignment::Root) .await?; let rel = PgClass::with_oid(portal.class_oid) .fetch_one(&mut workspace_client) .await?; let attrs: HashMap = PgAttribute::all_for_rel(portal.class_oid) .fetch_all(&mut workspace_client) .await? .into_iter() .map(|value| (value.attname.clone(), value)) .collect(); // TODO: implement with sql join let mut fields: Vec = vec![]; for field in Field::belonging_to_portal(portal_id) .fetch_all(&mut app_db) .await? { let attr = attrs.get(&field.name); let prompts: HashMap = FieldFormPrompt::belonging_to_field(field.id) .fetch_all(&mut app_db) .await? .into_iter() .map(|prompt| (prompt.language, prompt.content)) .collect(); fields.push(FormFieldInfo { field, column_present: attr.is_some(), has_default: attr.is_some_and(|value| value.atthasdef), not_null: attr.is_some_and(|value| value.attnotnull.is_some_and(|notnull| notnull)), prompts, }) } let mut prompts_html: HashMap = HashMap::new(); for field in fields.iter() { // TODO: i18n let prompt = field .prompts .get(&Language::Eng) .cloned() .unwrap_or_default(); let prompt_md = markdown::to_html(&prompt); // TODO: a11y (input labels) prompts_html.insert(field.field.name.clone(), prompt_md); } #[derive(Debug, Template)] #[template(path = "forms/form_index.html")] struct ResponseTemplate { fields: Vec, language: Language, portal: Portal, prompts_html: HashMap, settings: Settings, } Ok(Html( ResponseTemplate { fields, language: Language::Eng, portal, prompts_html, settings, } .render()?, ) .into_response()) }