2025-07-08 14:37:03 -07:00
|
|
|
use std::collections::HashMap;
|
|
|
|
|
|
|
|
|
|
use askama::Template;
|
|
|
|
|
use axum::{
|
2025-07-08 16:54:51 -07:00
|
|
|
Json,
|
2025-07-08 14:37:03 -07:00
|
|
|
extract::{Path, State},
|
2025-07-18 16:20:03 -07:00
|
|
|
response::{Html, IntoResponse, Redirect, Response},
|
2025-07-08 14:37:03 -07:00
|
|
|
};
|
|
|
|
|
use axum_extra::extract::Form;
|
|
|
|
|
use interim_models::{
|
2025-07-18 16:20:03 -07:00
|
|
|
field::{Encodable, Field, FieldType, InsertableFieldBuilder, RFC_3339_S},
|
2025-07-08 14:37:03 -07:00
|
|
|
lens::{Lens, LensDisplayType},
|
2025-07-08 16:54:51 -07:00
|
|
|
};
|
|
|
|
|
use interim_pgtypes::{
|
2025-07-18 16:20:03 -07:00
|
|
|
escape_identifier,
|
2025-07-08 16:54:51 -07:00
|
|
|
pg_attribute::{PgAttribute, fetch_attributes_for_rel, fetch_primary_keys_for_rel},
|
|
|
|
|
pg_class::PgClass,
|
2025-07-08 14:37:03 -07:00
|
|
|
};
|
|
|
|
|
use serde::Deserialize;
|
2025-07-08 16:54:51 -07:00
|
|
|
use serde_json::json;
|
2025-07-08 14:37:03 -07:00
|
|
|
use sqlx::{
|
|
|
|
|
postgres::{PgRow, types::Oid},
|
|
|
|
|
query,
|
|
|
|
|
};
|
|
|
|
|
use uuid::Uuid;
|
|
|
|
|
|
|
|
|
|
use crate::{
|
2025-07-18 16:20:03 -07:00
|
|
|
app_error::{AppError, bad_request, not_found},
|
2025-07-08 14:37:03 -07:00
|
|
|
app_state::AppDbConn,
|
|
|
|
|
base_pooler::BasePooler,
|
|
|
|
|
bases::Base,
|
2025-07-18 16:20:03 -07:00
|
|
|
db_conns::init_role,
|
|
|
|
|
navigator::Navigator,
|
2025-07-08 14:37:03 -07:00
|
|
|
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(
|
2025-07-18 16:20:03 -07:00
|
|
|
State(mut base_pooler): State<BasePooler>,
|
|
|
|
|
navigator: Navigator,
|
2025-07-08 14:37:03 -07:00
|
|
|
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
|
2025-07-18 16:20:03 -07:00
|
|
|
|
|
|
|
|
let mut client = base_pooler.acquire_for(base_id).await?;
|
|
|
|
|
|
|
|
|
|
let attrs = fetch_attributes_for_rel(Oid(class_oid), &mut *client).await?;
|
|
|
|
|
|
2025-07-08 14:37:03 -07:00
|
|
|
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?;
|
2025-07-18 16:20:03 -07:00
|
|
|
|
|
|
|
|
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())
|
2025-07-08 14:37:03 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[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,
|
2025-07-18 16:20:03 -07:00
|
|
|
Path(LensPagePath { lens_id, .. }): Path<LensPagePath>,
|
2025-07-08 14:37:03 -07:00
|
|
|
) -> Result<Response, AppError> {
|
|
|
|
|
// FIXME auth
|
2025-07-18 16:20:03 -07:00
|
|
|
let lens = Lens::fetch_by_id(lens_id, &mut *app_db)
|
2025-07-08 14:37:03 -07:00
|
|
|
.await?
|
2025-07-18 16:20:03 -07:00
|
|
|
.ok_or(not_found!("lens not found"))?;
|
|
|
|
|
let base = Base::fetch_by_id(lens.base_id, &mut *app_db)
|
|
|
|
|
.await?
|
|
|
|
|
.ok_or(not_found!("no base found with that id"))?;
|
2025-07-08 14:37:03 -07:00
|
|
|
|
2025-07-18 16:20:03 -07:00
|
|
|
let mut client = base_pooler.acquire_for(lens.base_id).await?;
|
2025-07-08 14:37:03 -07:00
|
|
|
init_role(
|
|
|
|
|
&format!("{}{}", &base.user_role_prefix, ¤t_user.id.simple()),
|
|
|
|
|
&mut client,
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
|
2025-07-18 16:20:03 -07:00
|
|
|
let class = PgClass::fetch_by_oid(lens.class_oid, &mut *client)
|
2025-07-08 16:54:51 -07:00
|
|
|
.await?
|
|
|
|
|
.ok_or(AppError::NotFound(
|
|
|
|
|
"no relation found with that oid".to_owned(),
|
|
|
|
|
))?;
|
|
|
|
|
let namespace = class.fetch_namespace(&mut *client).await?;
|
2025-07-08 14:37:03 -07:00
|
|
|
|
|
|
|
|
let lens = Lens::fetch_by_id(lens_id, &mut *app_db)
|
|
|
|
|
.await?
|
|
|
|
|
.ok_or(not_found!("no lens found with that id"))?;
|
|
|
|
|
|
2025-07-18 16:20:03 -07:00
|
|
|
let attrs = fetch_attributes_for_rel(lens.class_oid, &mut *client).await?;
|
|
|
|
|
let fields = lens.fetch_fields(&mut *app_db).await?;
|
|
|
|
|
let pkey_attrs = fetch_primary_keys_for_rel(lens.class_oid, &mut *client).await?;
|
2025-07-08 16:54:51 -07:00
|
|
|
|
2025-07-08 14:37:03 -07:00
|
|
|
const FRONTEND_ROW_LIMIT: i64 = 1000;
|
2025-07-08 16:54:51 -07:00
|
|
|
struct Row {
|
|
|
|
|
pkeys: HashMap<String, Encodable>,
|
|
|
|
|
data: PgRow,
|
|
|
|
|
}
|
|
|
|
|
let rows: Vec<Row> = query(&format!(
|
|
|
|
|
"select {0} from {1}.{2} limit $1",
|
|
|
|
|
pkey_attrs
|
2025-07-08 14:37:03 -07:00
|
|
|
.iter()
|
2025-07-08 16:54:51 -07:00
|
|
|
.chain(attrs.iter())
|
|
|
|
|
.map(|attr| escape_identifier(&attr.attname))
|
2025-07-08 14:37:03 -07:00
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
.join(", "),
|
2025-07-08 16:54:51 -07:00
|
|
|
escape_identifier(&namespace.nspname),
|
2025-07-08 14:37:03 -07:00
|
|
|
escape_identifier(&class.relname),
|
|
|
|
|
))
|
|
|
|
|
.bind(FRONTEND_ROW_LIMIT)
|
|
|
|
|
.fetch_all(&mut *client)
|
2025-07-08 16:54:51 -07:00
|
|
|
.await?
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|row| {
|
|
|
|
|
let mut pkey_values: HashMap<String, Encodable> = 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());
|
|
|
|
|
}
|
|
|
|
|
Row {
|
|
|
|
|
pkeys: pkey_values,
|
|
|
|
|
data: row,
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
|
2025-07-08 14:37:03 -07:00
|
|
|
#[derive(Template)]
|
|
|
|
|
#[template(path = "lens.html")]
|
|
|
|
|
struct ResponseTemplate {
|
|
|
|
|
fields: Vec<Field>,
|
|
|
|
|
all_columns: Vec<PgAttribute>,
|
2025-07-08 16:54:51 -07:00
|
|
|
rows: Vec<Row>,
|
2025-07-08 14:37:03 -07:00
|
|
|
settings: Settings,
|
|
|
|
|
}
|
|
|
|
|
Ok(Html(
|
|
|
|
|
ResponseTemplate {
|
|
|
|
|
all_columns: attrs,
|
|
|
|
|
fields,
|
|
|
|
|
rows,
|
|
|
|
|
settings,
|
|
|
|
|
}
|
|
|
|
|
.render()?,
|
|
|
|
|
)
|
|
|
|
|
.into_response())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Deserialize)]
|
2025-07-18 16:20:03 -07:00
|
|
|
pub struct AddColumnPageForm {
|
|
|
|
|
name: String,
|
|
|
|
|
label: String,
|
|
|
|
|
field_type: String,
|
|
|
|
|
timestamp_format: Option<String>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn try_field_type_from_form(form: &AddColumnPageForm) -> Result<FieldType, AppError> {
|
|
|
|
|
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")))
|
2025-07-08 14:37:03 -07:00
|
|
|
}
|
|
|
|
|
|
2025-07-18 16:20:03 -07:00
|
|
|
pub async fn add_column_page_post(
|
2025-07-08 14:37:03 -07:00
|
|
|
State(settings): State<Settings>,
|
2025-07-18 16:20:03 -07:00
|
|
|
State(mut base_pooler): State<BasePooler>,
|
|
|
|
|
navigator: Navigator,
|
2025-07-08 14:37:03 -07:00
|
|
|
AppDbConn(mut app_db): AppDbConn,
|
|
|
|
|
CurrentUser(current_user): CurrentUser,
|
2025-07-18 16:20:03 -07:00
|
|
|
Path(LensPagePath { lens_id, .. }): Path<LensPagePath>,
|
|
|
|
|
Form(form): Form<AddColumnPageForm>,
|
2025-07-08 14:37:03 -07:00
|
|
|
) -> Result<Response, AppError> {
|
|
|
|
|
// FIXME auth
|
|
|
|
|
// FIXME csrf
|
2025-07-18 16:20:03 -07:00
|
|
|
// FIXME validate column name length is less than 64
|
2025-07-08 14:37:03 -07:00
|
|
|
|
|
|
|
|
let lens = Lens::fetch_by_id(lens_id, &mut *app_db)
|
|
|
|
|
.await?
|
|
|
|
|
.ok_or(not_found!("lens not found"))?;
|
2025-07-18 16:20:03 -07:00
|
|
|
let base = Base::fetch_by_id(lens.base_id, &mut *app_db)
|
|
|
|
|
.await?
|
|
|
|
|
.ok_or(not_found!("no base found with that id"))?;
|
|
|
|
|
|
|
|
|
|
let mut client = base_pooler.acquire_for(base.id).await?;
|
|
|
|
|
init_role(
|
|
|
|
|
&format!("{}{}", &base.user_role_prefix, ¤t_user.id.simple()),
|
|
|
|
|
&mut client,
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
let class = PgClass::fetch_by_oid(lens.class_oid, &mut *client)
|
|
|
|
|
.await?
|
|
|
|
|
.ok_or(not_found!("pg class not found"))?;
|
|
|
|
|
|
|
|
|
|
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(&mut *client)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
Field::insertable_builder()
|
2025-07-08 14:37:03 -07:00
|
|
|
.lens_id(lens.id)
|
2025-07-18 16:20:03 -07:00
|
|
|
.name(form.name)
|
|
|
|
|
.label(if form.label.is_empty() {
|
|
|
|
|
None
|
|
|
|
|
} else {
|
|
|
|
|
Some(form.label)
|
|
|
|
|
})
|
|
|
|
|
.field_type(field_type)
|
2025-07-08 14:37:03 -07:00
|
|
|
.build()?
|
|
|
|
|
.insert(&mut *app_db)
|
|
|
|
|
.await?;
|
|
|
|
|
|
2025-07-18 16:20:03 -07:00
|
|
|
Ok(navigator.lens_page(&lens).redirect_to())
|
2025-07-08 14:37:03 -07:00
|
|
|
}
|
|
|
|
|
|
2025-07-18 16:20:03 -07:00
|
|
|
// #[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())
|
|
|
|
|
// }
|
|
|
|
|
|
2025-07-08 14:37:03 -07:00
|
|
|
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())
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-08 16:54:51 -07:00
|
|
|
#[derive(Deserialize)]
|
|
|
|
|
pub struct UpdateValuePageForm {
|
|
|
|
|
column: String,
|
|
|
|
|
pkeys: HashMap<String, Encodable>,
|
|
|
|
|
value: Encodable,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub async fn update_value_page_post(
|
|
|
|
|
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>,
|
|
|
|
|
Json(body): Json<UpdateValuePageForm>,
|
|
|
|
|
) -> Result<Response, AppError> {
|
|
|
|
|
// FIXME auth
|
|
|
|
|
// FIXME csrf
|
|
|
|
|
|
|
|
|
|
let base = Base::fetch_by_id(base_id, &mut *app_db)
|
|
|
|
|
.await?
|
|
|
|
|
.ok_or(not_found!("no base found with that id"))?;
|
|
|
|
|
|
|
|
|
|
let mut client = base_pooler.acquire_for(base_id).await?;
|
|
|
|
|
|
|
|
|
|
let class = PgClass::fetch_by_oid(Oid(class_oid), &mut *client)
|
|
|
|
|
.await?
|
|
|
|
|
.ok_or(not_found!("unable to load table"))?;
|
|
|
|
|
let namespace = class.fetch_namespace(&mut *client).await?;
|
|
|
|
|
|
|
|
|
|
let pkey_attrs = fetch_primary_keys_for_rel(Oid(class_oid), &mut *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(&namespace.nspname),
|
|
|
|
|
escape_identifier(&class.relname),
|
|
|
|
|
escape_identifier(&body.column),
|
|
|
|
|
escape_identifier(&pkey_attrs.first().unwrap().attname),
|
|
|
|
|
))))
|
|
|
|
|
.execute(&mut *client)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
Ok(Json(json!({ "ok": true })).into_response())
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-08 14:37:03 -07:00
|
|
|
#[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");
|
|
|
|
|
}
|