use std::collections::HashMap; use askama::Template; use axum::{ extract::{Path, State}, response::{Html, IntoResponse as _, Redirect, Response}, }; use axum_extra::extract::Form; use interim_models::{ lens::{Lens, LensDisplayType}, selection::{AttrFilter, Field, Selection}, }; use interim_pgtypes::pg_attribute::{PgAttribute, fetch_attributes_for_rel}; use serde::Deserialize; use sqlx::{ postgres::{PgRow, types::Oid}, query, }; use uuid::Uuid; use crate::{ app_error::{AppError, not_found}, app_state::AppDbConn, base_pooler::BasePooler, bases::Base, data_layer::{ToHtmlString as _, Value}, db_conns::{escape_identifier, init_role}, settings::Settings, users::CurrentUser, }; #[derive(Deserialize)] pub struct LensesPagePath { base_id: Uuid, class_oid: u32, } pub async fn lenses_page( State(settings): State, AppDbConn(mut app_db): AppDbConn, Path(LensesPagePath { base_id, class_oid }): Path, ) -> Result { // FIXME auth let lenses = Lens::fetch_by_rel(base_id, Oid(class_oid), &mut *app_db).await?; #[derive(Template)] #[template(path = "lenses.html")] struct ResponseTemplate { base_id: Uuid, class_oid: u32, lenses: Vec, settings: Settings, } Ok(Html( ResponseTemplate { base_id, class_oid, lenses, settings, } .render()?, ) .into_response()) } pub async fn add_lens_page_get( State(settings): State, AppDbConn(mut app_db): AppDbConn, Path(LensesPagePath { base_id, class_oid }): Path, ) -> Result { // FIXME auth #[derive(Template)] #[template(path = "add_lens.html")] struct ResponseTemplate { base_id: Uuid, class_oid: u32, settings: Settings, } Ok(Html( ResponseTemplate { base_id, class_oid, settings, } .render()?, ) .into_response()) } #[derive(Deserialize)] pub struct AddLensPagePostForm { name: String, } pub async fn add_lens_page_post( State(Settings { root_path, .. }): State, AppDbConn(mut app_db): AppDbConn, Path(LensesPagePath { base_id, class_oid }): Path, Form(AddLensPagePostForm { name }): Form, ) -> Result { // FIXME auth // FIXME csrf let lens = Lens::insertable_builder() .base_id(base_id) .class_oid(Oid(class_oid)) .name(name) .display_type(LensDisplayType::Table) .build()? .insert(&mut *app_db) .await?; Ok(Redirect::to(&format!( "{root_path}/d/{0}/r/{class_oid}/l/{1}", base_id.simple(), lens.id.simple() )) .into_response()) } #[derive(Deserialize)] pub struct LensPagePath { base_id: Uuid, class_oid: u32, lens_id: Uuid, } pub async fn lens_page( State(settings): State, State(mut base_pooler): State, AppDbConn(mut app_db): AppDbConn, CurrentUser(current_user): CurrentUser, Path(LensPagePath { base_id, class_oid, lens_id, }): Path, ) -> Result { // FIXME auth let base = Base::fetch_by_id(base_id, &mut *app_db) .await? .ok_or(AppError::NotFound("no base found with that id".to_owned()))?; let mut client = base_pooler.acquire_for(base_id).await?; init_role( &format!("{}{}", &base.user_role_prefix, ¤t_user.id.simple()), &mut client, ) .await?; // FIXME auth let class = query!( "select relname from pg_class where oid = $1", Oid(class_oid) ) .fetch_optional(&mut *client) .await? .ok_or(AppError::NotFound( "no relation found with that oid".to_owned(), ))?; let lens = Lens::fetch_by_id(lens_id, &mut *app_db) .await? .ok_or(not_found!("no lens found with that id"))?; let attrs = fetch_attributes_for_rel(Oid(class_oid), &mut *client).await?; let selections = lens.fetch_selections(&mut *app_db).await?; let mut fields: Vec = Vec::with_capacity(selections.len()); for selection in selections.clone() { fields.append(&mut selection.resolve_fields_from_attrs(&attrs)); } const FRONTEND_ROW_LIMIT: i64 = 1000; let rows = query(&format!( "select {} from {} limit $1", attrs .iter() .map(|attr| attr.attname.clone()) .collect::>() .join(", "), escape_identifier(&class.relname), )) .bind(FRONTEND_ROW_LIMIT) .fetch_all(&mut *client) .await?; #[derive(Template)] #[template(path = "lens.html")] struct ResponseTemplate { fields: Vec, all_columns: Vec, rows: Vec, selections_json: String, settings: Settings, } Ok(Html( ResponseTemplate { all_columns: attrs, fields, rows, selections_json: serde_json::to_string(&selections)?, settings, } .render()?, ) .into_response()) } #[derive(Debug, Deserialize)] pub struct AddSelectionPageForm { column: String, } pub async fn add_selection_page_post( State(settings): State, AppDbConn(mut app_db): AppDbConn, CurrentUser(current_user): CurrentUser, Path(LensPagePath { base_id, class_oid, lens_id, }): Path, Form(form): Form, ) -> Result { dbg!(&form); // FIXME auth // FIXME csrf let lens = Lens::fetch_by_id(lens_id, &mut *app_db) .await? .ok_or(not_found!("lens not found"))?; Selection::insertable_builder() .lens_id(lens.id) .attr_filters(vec![AttrFilter::NameEq(form.column)]) .build()? .insert(&mut *app_db) .await?; Ok(Redirect::to(&format!( "{0}/d/{base_id}/r/{class_oid}/l/{lens_id}/", settings.root_path )) .into_response()) } pub async fn update_lens_page_post( State(settings): State, AppDbConn(mut app_db): AppDbConn, CurrentUser(current_user): CurrentUser, Path(LensPagePath { base_id, class_oid, lens_id, }): Path, Form(form): Form>, ) -> Result { dbg!(&form); // FIXME auth // FIXME csrf Ok(Redirect::to(&format!( "{0}/d/{base_id}/r/{class_oid}/l/{lens_id}/", settings.root_path )) .into_response()) } #[derive(Deserialize)] pub struct ViewerPagePath { base_id: Uuid, class_oid: u32, lens_id: Uuid, } pub async fn viewer_page( State(settings): State, State(mut base_pooler): State, AppDbConn(mut app_db): AppDbConn, CurrentUser(current_user): CurrentUser, Path(params): Path, ) -> Result { todo!("not yet implemented"); }