a small relational database with user-editable schema for manual data entry
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
yopa/yopa-web/src/routes/models/property.rs

302 lines
9.3 KiB

use actix_session::Session;
use actix_web::{web, Responder};
use serde::{Deserialize, Serialize};
use yopa::model::{PropertyModel, PropertyOptions};
use yopa::{DataType, TypedValue, ID};
use crate::routes::models::relation::ObjectOrRelationModelDisplay;
use crate::session_ext::SessionExt;
use crate::tera_ext::TeraExt;
use crate::utils::{redirect, StorageErrorIntoResponseError};
use crate::TERA;
#[get("/model/property/create/{object_id}")]
pub(crate) async fn create_form(
id: web::Path<ID>,
store: crate::YopaStoreWrapper,
session: Session,
) -> actix_web::Result<impl Responder> {
let mut context = tera::Context::new();
session.render_flash(&mut context);
let rg = store.read().await;
// Re-fill old values
if let Ok(Some(form)) = session.take::<PropertyModelCreateForm>("old") {
context.insert("old", &form);
} else {
context.insert(
"old",
// This is the defaults for the form
&PropertyModelCreateForm {
object: Default::default(),
name: "".to_string(),
optional: true,
multiple: false,
unique: false,
data_type: DataType::String,
default: "".to_string(),
sort_key: 1000, // big number so it goes at the end by default
opt_multiline: false,
},
);
}
debug!("ID = {}", id);
let object = {
debug!("Create property for ID={}", id);
if let Some(om) = rg.get_object_model(*id) {
ObjectOrRelationModelDisplay {
id: om.id,
describe: format!("object model \"{}\"", om.name),
}
} else if let Some(rm) = rg.get_relation_model(*id) {
ObjectOrRelationModelDisplay {
id: rm.id,
describe: format!("relation model \"{}\"", rm.name),
}
} else {
return Err(actix_web::error::ErrorNotFound("No such source object"));
}
};
context.insert("object", &object);
TERA.build_response("models/property_create", &context)
}
#[derive(Serialize, Deserialize)]
pub(crate) struct PropertyModelCreateForm {
pub object: ID,
pub name: String,
#[serde(default)]
pub optional: bool,
#[serde(default)]
pub multiple: bool,
#[serde(default)]
pub unique: bool,
pub data_type: DataType,
/// Default value to be parsed to the data type
/// May be unused if empty and optional
pub default: String,
#[serde(default)]
pub sort_key: i64,
#[serde(default)]
pub opt_multiline: bool,
}
fn parse_default(data_type: DataType, default: String) -> Result<TypedValue, String> {
Ok(match data_type {
DataType::String => TypedValue::String(default.into()),
DataType::Integer => {
if default.is_empty() {
TypedValue::Integer(0)
} else {
// TODO better error reporting
TypedValue::Integer(
default
.parse()
.map_err(|_| format!("Error parsing \"{}\" as integer", default))?,
)
}
}
DataType::Decimal => {
if default.is_empty() {
TypedValue::Decimal(0.0)
} else {
// TODO better error reporting
TypedValue::Decimal(
default
.parse()
.map_err(|_| format!("Error parsing \"{}\" as decimal", default))?,
)
}
}
DataType::Boolean => {
if default.is_empty() {
TypedValue::Boolean(false)
} else {
TypedValue::String(default.clone().into())
.cast_to(DataType::Boolean)
.map_err(|_| format!("Error parsing \"{}\" as boolean", default))?
}
}
})
}
#[post("/model/property/create")]
pub(crate) async fn create(
form: web::Form<PropertyModelCreateForm>,
store: crate::YopaStoreWrapper,
session: Session,
) -> actix_web::Result<impl Responder> {
let mut wg = store.write().await;
let form = form.into_inner();
let optional = form.optional;
let multiple = form.multiple;
let unique = form.unique;
let default = match parse_default(form.data_type, form.default.clone()) {
Ok(def) => def,
Err(msg) => {
warn!("{}", msg);
session.flash_error(msg);
session.set("old", &form).unwrap();
return redirect(format!("/model/property/create/{}", form.object));
}
};
match wg.define_property(PropertyModel {
id: Default::default(),
object: form.object,
name: form.name.clone(),
optional,
multiple,
unique,
data_type: form.data_type,
default,
sort_key: form.sort_key,
options: PropertyOptions {
multiline: form.opt_multiline
}
}) {
Ok(_id) => {
wg.persist().err_to_500()?;
debug!("Property created, redirecting to root");
session.flash_success(format!("Property model \"{}\" created.", form.name));
redirect("/models")
}
Err(e) => {
warn!("Error creating property model: {}", e);
session.flash_error(e.to_string());
session.set("old", &form).unwrap();
redirect(format!("/model/property/create/{}", form.object))
}
}
}
#[get("/model/property/delete/{id}")]
pub(crate) async fn delete(
id: web::Path<ID>,
store: crate::YopaStoreWrapper,
session: Session,
) -> actix_web::Result<impl Responder> {
let mut wg = store.write().await;
match wg.undefine_property(*id) {
Ok(rm) => {
wg.persist().err_to_500()?;
debug!("Property deleted, redirecting to root");
session.flash_success(format!("Property \"{}\" deleted.", rm.name));
redirect("/models")
}
Err(e) => {
warn!("Error deleting property: {}", e);
session.flash_error(e.to_string());
redirect("/models") // back?
}
}
}
#[derive(Serialize, Deserialize)]
pub(crate) struct PropertyModelEditForm {
pub name: String,
#[serde(default)]
pub optional: bool,
#[serde(default)]
pub multiple: bool,
#[serde(default)]
pub unique: bool,
pub data_type: DataType,
/// Default value to be parsed to the data type
/// May be unused if empty and optional
pub default: String,
#[serde(default)]
pub sort_key: i64,
#[serde(default)]
pub opt_multiline: bool,
}
#[get("/model/property/update/{model_id}")]
pub(crate) async fn update_form(
model_id: web::Path<ID>,
store: crate::YopaStoreWrapper,
session: Session,
) -> actix_web::Result<impl Responder> {
let mut context = tera::Context::new();
session.render_flash(&mut context);
let rg = store.read().await;
let model = rg
.get_property_model(*model_id)
.ok_or_else(|| actix_web::error::ErrorNotFound("No such model"))?;
// Re-fill old values
if let Ok(Some(form)) = session.take::<PropertyModelEditForm>("old") {
let mut model = model.clone();
model.name = form.name;
model.data_type = form.data_type;
model.default = TypedValue::String(form.default.into());
model.optional = form.optional;
model.multiple = form.multiple;
model.unique = form.unique;
model.sort_key = form.sort_key;
model.options.multiline = form.opt_multiline;
context.insert("model", &model);
} else {
context.insert("model", &model);
}
TERA.build_response("models/property_update", &context)
}
#[post("/model/property/update/{model_id}")]
pub(crate) async fn update(
model_id: web::Path<ID>,
form: web::Form<PropertyModelEditForm>,
store: crate::YopaStoreWrapper,
session: Session,
) -> actix_web::Result<impl Responder> {
let mut wg = store.write().await;
let form = form.into_inner();
let id = model_id.into_inner();
let default = match parse_default(form.data_type, form.default.clone()) {
Ok(def) => def,
Err(msg) => {
warn!("{}", msg);
session.flash_error(msg);
session.set("old", form).unwrap();
return redirect(format!("/model/property/update/{}", id));
}
};
match wg.update_property_model(PropertyModel {
id,
object: Default::default(), // dummy
name: form.name.clone(),
optional: form.optional,
multiple: form.multiple,
unique: form.unique,
data_type: form.data_type,
default,
sort_key: form.sort_key,
options: PropertyOptions {
multiline: form.opt_multiline
}
}) {
Ok(_id) => {
wg.persist().err_to_500()?;
debug!("Property updated, redirecting to root");
session.flash_success(format!("Property \"{}\" updated.", form.name));
redirect("/models")
}
Err(e) => {
warn!("Error updating model: {}", e);
session.flash_error(e.to_string());
session.set("old", form).unwrap();
redirect(format!("/model/property/update/{}", id))
}
}
}