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, store: crate::YopaStoreWrapper, session: Session, ) -> actix_web::Result { 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::("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 { 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, store: crate::YopaStoreWrapper, session: Session, ) -> actix_web::Result { 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, store: crate::YopaStoreWrapper, session: Session, ) -> actix_web::Result { 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, store: crate::YopaStoreWrapper, session: Session, ) -> actix_web::Result { 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::("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, form: web::Form, store: crate::YopaStoreWrapper, session: Session, ) -> actix_web::Result { 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)) } } }