use actix_session::Session; use actix_web::{web, HttpResponse, Responder}; use serde::{Deserialize, Serialize}; use yopa::model::{ObjectModel, PropertyModel}; use yopa::ID; use crate::routes::models::relation::RelationModelDisplay; use crate::session_ext::SessionExt; use crate::tera_ext::TeraExt; use crate::utils::{redirect, StorageErrorIntoResponseError}; use crate::TERA; use itertools::Itertools; #[derive(Serialize, Debug)] pub(crate) struct ObjectModelDisplay<'a> { pub(crate) id: yopa::ID, pub(crate) name: &'a str, #[serde(skip)] pub(crate) model : &'a ObjectModel, pub(crate) properties: Vec<&'a PropertyModel>, pub(crate) relations: Vec>, pub(crate) reciprocal_relations: Vec>, } #[get("/model/object/create")] pub(crate) async fn create_form(session: Session) -> actix_web::Result { let mut context = tera::Context::new(); session.render_flash(&mut context); // Re-fill old values if let Ok(Some(form)) = session.take::("old") { context.insert("old", &form); } else { let mut f = ObjectModelForm::default(); f.sort_key = 1000; context.insert("old", &f); } TERA.build_response("models/model_create", &context) } #[derive(Default, Debug, Clone, Serialize, Deserialize)] pub(crate) struct ObjectModelForm { pub name: String, #[serde(default)] // #[serde(with="serde_with::rust::default_on_error")] // This is because "" can be selected #[serde(with = "my_string_empty_as_none")] pub name_property: Option, #[serde(default)] pub sort_key: i64, } #[post("/model/object/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(); match wg.define_object(ObjectModel { id: Default::default(), name: form.name.clone(), name_property: form.name_property, sort_key: form.sort_key }) { Ok(_id) => { wg.persist().err_to_500()?; debug!("Object created, redirecting to root"); session.flash_success(format!("Object model \"{}\" created.", form.name)); redirect("/models") } Err(e) => { warn!("Error creating model: {}", e); session.flash_error(e.to_string()); session.set("old", form).unwrap(); redirect("/model/object/create") } } } #[get("/model/object/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_object_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(); context.insert("old", &form); model.name = form.name; model.name_property = form.name_property; context.insert("model", &model); } else { context.insert("model", model); context.insert( "old", &ObjectModelForm { name: model.name.to_string(), name_property: model.name_property, sort_key: model.sort_key, }, ); } let properties = rg.get_property_models_for_parent(*model_id).collect_vec(); context.insert("properties", &properties); TERA.build_response("models/model_update", &context) } #[post("/model/object/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(); match wg.update_object_model(ObjectModel { id, name: form.name.clone(), name_property: form.name_property, sort_key: form.sort_key, }) { Ok(_id) => { wg.persist().err_to_500()?; debug!("Object updated, redirecting to root"); session.flash_success(format!("Object model \"{}\" 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/object/update/{}", id)) } } } #[get("/model/object/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_object(*id) { Ok(om) => { wg.persist().err_to_500()?; debug!("Object model deleted, redirecting to root"); session.flash_success(format!("Object model \"{}\" deleted.", om.name)); redirect("/models") } Err(e) => { warn!("Error deleting object model: {}", e); session.flash_error(e.to_string()); redirect("/models") // back? } } } pub mod my_string_empty_as_none { use serde::de::{Error, Visitor}; use serde::{Deserializer, Serialize, Serializer}; use std::fmt; use std::fmt::Display; use std::marker::PhantomData; use std::str::FromStr; use yopa::ID; // FIXME largely copied from serde_with /// Deserialize an `Option` from a string using `FromStr` pub fn deserialize<'de, D, S>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, S: FromStr, S::Err: Display, { struct OptionStringEmptyNone(PhantomData); impl<'de, S> Visitor<'de> for OptionStringEmptyNone where S: FromStr, S::Err: Display, { type Value = Option; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("any string") } fn visit_str(self, value: &str) -> Result where E: Error, { match value { "" => Ok(None), v => S::from_str(v).map(Some).map_err(Error::custom), } } fn visit_string(self, value: String) -> Result where E: Error, { match &*value { "" => Ok(None), v => S::from_str(v).map(Some).map_err(Error::custom), } } // TODO remove? fn visit_u64(self, v: u64) -> Result where E: Error, { self.visit_str(&v.to_string()) } // handles the `null` case fn visit_unit(self) -> Result where E: Error, { Ok(None) } } deserializer.deserialize_any(OptionStringEmptyNone(PhantomData)) } /// Serialize a string from `Option` using `AsRef` or using the empty string if `None`. pub fn serialize(option: &Option, serializer: S) -> Result where S: Serializer, { option.serialize(serializer) // if let Some(value) = option { // value.serialize(serializer) // } else { // "".serialize(serializer) // } } }