phonograph/interim-server/src/routes/lenses.rs

284 lines
7 KiB
Rust
Raw Normal View History

2025-07-08 14:37:03 -07:00
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<Settings>,
AppDbConn(mut app_db): AppDbConn,
Path(LensesPagePath { base_id, class_oid }): Path<LensesPagePath>,
) -> Result<Response, AppError> {
// 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<Lens>,
settings: Settings,
}
Ok(Html(
ResponseTemplate {
base_id,
class_oid,
lenses,
settings,
}
.render()?,
)
.into_response())
}
pub async fn add_lens_page_get(
State(settings): State<Settings>,
AppDbConn(mut app_db): AppDbConn,
Path(LensesPagePath { base_id, class_oid }): Path<LensesPagePath>,
) -> Result<Response, AppError> {
// 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<Settings>,
AppDbConn(mut app_db): AppDbConn,
Path(LensesPagePath { base_id, class_oid }): Path<LensesPagePath>,
Form(AddLensPagePostForm { name }): Form<AddLensPagePostForm>,
) -> Result<Response, AppError> {
// 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<Settings>,
State(mut base_pooler): State<BasePooler>,
AppDbConn(mut app_db): AppDbConn,
CurrentUser(current_user): CurrentUser,
Path(LensPagePath {
base_id,
class_oid,
lens_id,
}): Path<LensPagePath>,
) -> Result<Response, AppError> {
// 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, &current_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<Field> = 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::<Vec<_>>()
.join(", "),
escape_identifier(&class.relname),
))
.bind(FRONTEND_ROW_LIMIT)
.fetch_all(&mut *client)
.await?;
#[derive(Template)]
#[template(path = "lens.html")]
struct ResponseTemplate {
fields: Vec<Field>,
all_columns: Vec<PgAttribute>,
rows: Vec<PgRow>,
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<Settings>,
AppDbConn(mut app_db): AppDbConn,
CurrentUser(current_user): CurrentUser,
Path(LensPagePath {
base_id,
class_oid,
lens_id,
}): Path<LensPagePath>,
Form(form): Form<AddSelectionPageForm>,
) -> Result<Response, AppError> {
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<Settings>,
AppDbConn(mut app_db): AppDbConn,
CurrentUser(current_user): CurrentUser,
Path(LensPagePath {
base_id,
class_oid,
lens_id,
}): Path<LensPagePath>,
Form(form): Form<HashMap<String, String>>,
) -> Result<Response, AppError> {
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<Settings>,
State(mut base_pooler): State<BasePooler>,
AppDbConn(mut app_db): AppDbConn,
CurrentUser(current_user): CurrentUser,
Path(params): Path<ViewerPagePath>,
) -> Result<Response, AppError> {
todo!("not yet implemented");
}