use std::collections::HashMap; use askama::Template; use axum::{ Json, extract::{Path, State}, response::{Html, IntoResponse, Response}, }; use axum_extra::extract::Form; use interim_models::{ base::Base, field::{Encodable, Field, FieldType, InsertableFieldBuilder, RFC_3339_S}, lens::{Lens, LensDisplayType}, }; use interim_pgtypes::{escape_identifier, pg_attribute::PgAttribute, pg_class::PgClass}; use serde::Deserialize; use serde_json::json; use sqlx::{ postgres::{PgRow, types::Oid}, query, }; use uuid::Uuid; use crate::{ app_error::{AppError, bad_request}, app_state::AppDbConn, base_pooler::{BasePooler, RoleAssignment}, navbar::{NavLocation, Navbar, RelLocation}, navigator::Navigator, settings::Settings, user::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::belonging_to_base(base_id) .belonging_to_rel(Oid(class_oid)) .fetch_all(&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): State, State(mut base_pooler): State, navigator: Navigator, AppDbConn(mut app_db): AppDbConn, CurrentUser(current_user): CurrentUser, Path(LensesPagePath { base_id, class_oid }): Path, Form(AddLensPagePostForm { name }): Form, ) -> Result { // FIXME auth // FIXME csrf let mut client = base_pooler .acquire_for(base_id, RoleAssignment::User(current_user.id)) .await?; let attrs = PgAttribute::all_for_rel(Oid(class_oid)) .fetch_all(&mut client) .await?; 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?; for attr in attrs { InsertableFieldBuilder::default_from_attr(&attr) .lens_id(lens.id) .build()? .insert(&mut app_db) .await?; } Ok(navigator.lens_page(&lens).redirect_to()) } #[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 { lens_id, base_id, class_oid, }): Path, ) -> Result { // FIXME auth let base = Base::with_id(base_id).fetch_one(&mut app_db).await?; let lens = Lens::with_id(lens_id).fetch_one(&mut app_db).await?; let mut base_client = base_pooler .acquire_for(lens.base_id, RoleAssignment::User(current_user.id)) .await?; let rel = PgClass::with_oid(lens.class_oid) .fetch_one(&mut base_client) .await?; let attrs = PgAttribute::all_for_rel(lens.class_oid) .fetch_all(&mut base_client) .await?; let fields = Field::belonging_to_lens(lens.id) .fetch_all(&mut app_db) .await?; let pkey_attrs = PgAttribute::pkeys_for_rel(lens.class_oid) .fetch_all(&mut base_client) .await?; const FRONTEND_ROW_LIMIT: i64 = 1000; let rows: Vec = query(&format!( "select {0} from {1}.{2} limit $1", pkey_attrs .iter() .chain(attrs.iter()) .map(|attr| escape_identifier(&attr.attname)) .collect::>() .join(", "), escape_identifier(&rel.regnamespace), escape_identifier(&rel.relname), )) .bind(FRONTEND_ROW_LIMIT) .fetch_all(base_client.get_conn()) .await?; let pkeys: Vec> = rows .iter() .map(|row| { let mut pkey_values: HashMap = HashMap::new(); for attr in pkey_attrs.clone() { let field = Field::default_from_attr(&attr); pkey_values.insert(field.name.clone(), field.get_value_encodable(row).unwrap()); } pkey_values }) .collect(); #[derive(Template)] #[template(path = "lens.html")] struct ResponseTemplate { fields: Vec, all_columns: Vec, rows: Vec, pkeys: Vec>, settings: Settings, navbar: Navbar, } Ok(Html( ResponseTemplate { all_columns: attrs, fields, pkeys, rows, navbar: Navbar::builder() .root_path(settings.root_path.clone()) .base(base.clone()) .populate_rels(&mut app_db, &mut base_client) .await? .current(NavLocation::Rel( Oid(class_oid), Some(RelLocation::Lens(lens.id)), )) .build()?, settings, } .render()?, ) .into_response()) } #[derive(Debug, Deserialize)] pub struct AddColumnPageForm { name: String, label: String, field_type: String, timestamp_format: Option, } fn try_field_type_from_form(form: &AddColumnPageForm) -> Result { let serialized = match form.field_type.as_str() { "Timestamp" => { json!({ "t": form.field_type, "c": { "format": form.timestamp_format.clone().unwrap_or(RFC_3339_S.to_owned()), }, }) } _ => json!({"t": form.field_type}), }; serde_json::from_value(serialized).or(Err(bad_request!("unable to parse field type"))) } pub async fn add_column_page_post( State(mut base_pooler): State, navigator: Navigator, AppDbConn(mut app_db): AppDbConn, CurrentUser(current_user): CurrentUser, Path(LensPagePath { lens_id, .. }): Path, Form(form): Form, ) -> Result { // FIXME auth // FIXME csrf // FIXME validate column name length is less than 64 let lens = Lens::with_id(lens_id).fetch_one(&mut app_db).await?; let base = Base::with_id(lens.base_id).fetch_one(&mut app_db).await?; let mut base_client = base_pooler .acquire_for(base.id, RoleAssignment::User(current_user.id)) .await?; let class = PgClass::with_oid(lens.class_oid) .fetch_one(&mut base_client) .await?; let field_type = try_field_type_from_form(&form)?; let data_type_fragment = field_type.attr_data_type_fragment().ok_or(bad_request!( "cannot create column with type specified as Unknown" ))?; query(&format!( r#" alter table {0} add column if not exists {1} {2} "#, class.get_identifier(), escape_identifier(&form.name), data_type_fragment )) .execute(base_client.get_conn()) .await?; Field::insertable_builder() .lens_id(lens.id) .name(form.name) .label(if form.label.is_empty() { None } else { Some(form.label) }) .field_type(field_type) .build()? .insert(&mut app_db) .await?; Ok(navigator.lens_page(&lens).redirect_to()) } // #[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()) // } #[derive(Deserialize)] pub struct UpdateValuePageForm { column: String, pkeys: HashMap, value: Encodable, } pub async fn update_value_page_post( State(mut base_pooler): State, CurrentUser(current_user): CurrentUser, Path(LensPagePath { base_id, class_oid, .. }): Path, Json(body): Json, ) -> Result { // FIXME auth // FIXME csrf let mut base_client = base_pooler .acquire_for(base_id, RoleAssignment::User(current_user.id)) .await?; let rel = PgClass::with_oid(Oid(class_oid)) .fetch_one(&mut base_client) .await?; let pkey_attrs = PgAttribute::pkeys_for_rel(rel.oid) .fetch_all(&mut base_client) .await?; body.pkeys .get(&pkey_attrs.first().unwrap().attname) .unwrap() .bind_onto(body.value.bind_onto(query(&format!( r#"update {0}.{1} set {2} = $1 where {3} = $2"#, escape_identifier(&rel.regnamespace), escape_identifier(&rel.relname), escape_identifier(&body.column), escape_identifier(&pkey_attrs.first().unwrap().attname), )))) .execute(base_client.get_conn()) .await?; Ok(Json(json!({ "ok": true })).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"); }