use actix_web::{web, HttpRequest, Responder, HttpResponse}; use crate::TERA; use crate::tera_ext::TeraExt; use yopa::{Storage, StorageError, ID, DataType, TypedValue}; use serde::{Deserialize, Serialize}; use yopa::model::{PropertyModel, RelationModel, ObjectModel}; use std::ops::DerefMut; use actix_session::Session; use crate::session_ext::SessionExt; use std::str::FromStr; use std::fmt::{Debug, Display}; use actix_web::http::header::IntoHeaderValue; #[derive(Serialize, Debug)] struct ObjectModelDisplay<'a> { id : yopa::ID, name : &'a str, properties: Vec<&'a PropertyModel>, relations: Vec>, } #[derive(Serialize, Debug)] struct RelationModelDisplay<'a> { model : &'a RelationModel, related_name : &'a str, properties: Vec<&'a PropertyModel>, } fn redirect(path : impl IntoHeaderValue) -> actix_web::Result { Ok(HttpResponse::SeeOther() .header("location", path) // back - to where? .finish()) } trait ParseOrBadReq { fn parse_or_bad_request(&self) -> actix_web::Result where T: FromStr, E: Display + Debug + 'static; } impl ParseOrBadReq for &str { fn parse_or_bad_request(&self) -> actix_web::Result where T: FromStr, E: Display + Debug + 'static { self.parse::() .map_err(|e| { error!("Parse error for \"{}\"", self); actix_web::error::ErrorBadRequest(e) }) } } impl ParseOrBadReq for String { fn parse_or_bad_request(&self) -> actix_web::Result where T: FromStr, E: Display + Debug + 'static { self.as_str() .parse_or_bad_request() } } #[get("/")] pub(crate) async fn index(session : Session, store : crate::YopaStoreWrapper) -> actix_web::Result { let rg = store.read().await; let models_iter = rg.get_object_models(); // object and relation props let mut model_props = rg.get_grouped_prop_models(); let mut model_relations = rg.get_grouped_relation_models(); let mut models = vec![]; for om in models_iter { let mut oprops = model_props.remove(&om.id).unwrap_or_default(); let mut relations = model_relations.remove(&om.id).unwrap_or_default(); let rel_displays = relations.into_iter().map(|rm| { let mut rprops = model_props.remove(&rm.id).unwrap_or_default(); rprops.sort_by_key(|m| &m.name); RelationModelDisplay { model: rm, related_name: rg.get_model_name(rm.related), properties: rprops } }).collect::>(); oprops.sort_by_key(|m| &m.name); models.push(ObjectModelDisplay { id: om.id, name: &om.name, properties: oprops, relations: rel_displays, }) } models.sort_by_key(|m| m.name); let mut ctx = tera::Context::new(); ctx.insert("models", &models); session.render_flash(&mut ctx); TERA.build_response("index", &ctx) } #[get("/model/object/create")] pub(crate) async fn object_model_create_form(session : Session) -> actix_web::Result { let mut context = tera::Context::new(); session.render_flash(&mut context); TERA.build_response("model_create", &context) } #[derive(Deserialize)] pub(crate) struct ObjectModelCreate { pub name : String, } #[post("/model/object/create")] pub(crate) async fn object_model_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() }) { Ok(_id) => { debug!("Object created, redirecting to root"); session.flash_success(format!("Object model \"{}\" created.", form.name)); redirect("/") } Err(e) => { warn!("Error creating model: {:?}", e); session.flash_error(e.to_string()); redirect("/model/object/create") } } } #[get("/model/relation/create/{object_id}")] pub(crate) async fn relation_model_create_form( object_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; debug!("ID = {}", object_id); let object = rg.get_object_model(object_id.parse_or_bad_request()?) .ok_or_else(|| actix_web::error::ErrorNotFound("No such source object"))?; let mut models: Vec<_> = rg.get_object_models().collect(); models.sort_by_key(|m| &m.name); context.insert("models", &models); context.insert("object", &object); TERA.build_response("relation_create", &context) } #[derive(Deserialize)] pub(crate) struct RelationModelCreate { pub object : ID, pub name : String, pub reciprocal_name : String, pub optional : Option, pub multiple : Option, pub related : ID, } #[post("/model/relation/create")] pub(crate) async fn relation_model_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_relation(RelationModel { id: Default::default(), object: form.object, name: form.name.clone(), reciprocal_name: form.reciprocal_name.clone(), optional: form.optional.unwrap_or_default() != 0, multiple: form.multiple.unwrap_or_default() != 0, related: form.related }) { Ok(_id) => { debug!("Relation created, redirecting to root"); session.flash_success(format!("Relation model \"{}\" created.", form.name)); redirect("/") } Err(e) => { warn!("Error creating relation model: {:?}", e); session.flash_error(e.to_string()); redirect(format!("/model/relation/create/{}", form.object)) } } } #[derive(Serialize, Debug)] struct ObjectOrRelationModelDisplay { id : ID, describe : String, } #[get("/model/property/create/{object_id}")] pub(crate) async fn property_model_create_form( object_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; debug!("ID = {}", object_id); let object = { let id = object_id.parse_or_bad_request()?; 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("property_create", &context) } #[derive(Deserialize)] pub(crate) struct PropertyModelCreate { pub object : ID, pub name : String, pub optional : Option, pub multiple : Option, pub data_type : DataType, /// Default value to be parsed to the data type /// May be unused if empty and optional pub default : String, } #[post("/model/property/create")] pub(crate) async fn property_model_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.unwrap_or_default() != 0; let multiple = form.multiple.unwrap_or_default() != 0; match wg.define_property(PropertyModel { id: Default::default(), object: form.object, name: form.name.clone(), optional, multiple, data_type: form.data_type, default: { match form.data_type { DataType::String => { if form.default.is_empty() && optional { None } else { Some(TypedValue::String(form.default.into())) } } DataType::Integer => { if form.default.is_empty() { if optional { None } else { Some(TypedValue::Integer(0)) } } else { // TODO better error reporting Some(TypedValue::Integer(form.default.parse() .map_err(|_| { actix_web::error::ErrorBadRequest(format!("Error parsing \"{}\" as integer", form.default)) })?)) } } DataType::Decimal => { if form.default.is_empty() { if optional { None } else { Some(TypedValue::Decimal(0.0)) } } else { // TODO better error reporting Some(TypedValue::Decimal(form.default.parse() .map_err(|_| { actix_web::error::ErrorBadRequest(format!("Error parsing \"{}\" as decimal", form.default)) })?)) } } DataType::Boolean => { if form.default.is_empty() { if optional { None } else { Some(TypedValue::Boolean(false)) } } else { Some(TypedValue::String(form.default.clone().into()) .cast_to(DataType::Boolean).map_err(|_| { actix_web::error::ErrorBadRequest(format!("Error parsing \"{}\" as boolean", form.default)) })?) } } } } }) { Ok(_id) => { debug!("Property created, redirecting to root"); session.flash_success(format!("Property model \"{}\" created.", form.name)); redirect("/") } Err(e) => { warn!("Error creating property model: {:?}", e); session.flash_error(e.to_string()); redirect(format!("/model/property/create/{}", form.object)) } } } #[get("/model/object/delete/{id}")] pub(crate) async fn object_model_delete( id : web::Path, store : crate::YopaStoreWrapper, session : Session ) -> actix_web::Result { let mut wg = store.write().await; match wg.undefine_object(id.parse().map_err(|e| actix_web::error::ErrorBadRequest(e))?) { Ok(om) => { debug!("Object model deleted, redirecting to root"); session.flash_success(format!("Object model \"{}\" deleted.", om.name)); redirect("/") } Err(e) => { warn!("Error deleting object model: {:?}", e); session.flash_error(e.to_string()); redirect("/") // back? } } } #[get("/model/relation/delete/{id}")] pub(crate) async fn relation_model_delete( id : web::Path, store : crate::YopaStoreWrapper, session : Session ) -> actix_web::Result { let mut wg = store.write().await; match wg.undefine_relation(id.parse_or_bad_request()?) { Ok(rm) => { debug!("Relation deleted, redirecting to root"); session.flash_success(format!("Relation model \"{}\" deleted.", rm.name)); redirect("/") } Err(e) => { warn!("Error deleting relation model: {:?}", e); session.flash_error(e.to_string()); redirect("/") // back? } } } #[get("/model/property/delete/{id}")] pub(crate) async fn property_model_delete( id : web::Path, store : crate::YopaStoreWrapper, session : Session ) -> actix_web::Result { let mut wg = store.write().await; match wg.undefine_property(id.parse_or_bad_request()?) { Ok(rm) => { debug!("Property deleted, redirecting to root"); session.flash_success(format!("Property \"{}\" deleted.", rm.name)); redirect("/") } Err(e) => { warn!("Error deleting property: {:?}", e); session.flash_error(e.to_string()); redirect("/") // back? } } }