diff --git a/Cargo.lock b/Cargo.lock index fc1dca3..8b7d81c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1185,6 +1185,18 @@ version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" +[[package]] +name = "json_dotpath" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e907e6c22a540a809d003178eae3172a6c54b94338058b9d72d051e53187be7" +dependencies = [ + "serde", + "serde_derive", + "serde_json", + "thiserror", +] + [[package]] name = "kernel32-sys" version = "0.2.2" @@ -2567,6 +2579,7 @@ dependencies = [ "heck", "include_dir", "itertools", + "json_dotpath", "log", "once_cell", "parking_lot", diff --git a/yopa-web/Cargo.toml b/yopa-web/Cargo.toml index 86fee3c..ba2099f 100644 --- a/yopa-web/Cargo.toml +++ b/yopa-web/Cargo.toml @@ -23,6 +23,7 @@ actix-web-static-files = "3.0" once_cell = "1.5.2" rand = "0.8.3" itertools = "0.10.0" +json_dotpath = "1.0.3" tokio = { version="0.2.6", features=["full"] } diff --git a/yopa-web/resources/templates/objects/index.html.tera b/yopa-web/resources/templates/objects/index.html.tera index 1c97278..92254bd 100644 --- a/yopa-web/resources/templates/objects/index.html.tera +++ b/yopa-web/resources/templates/objects/index.html.tera @@ -20,7 +20,7 @@ Objects diff --git a/yopa-web/resources/templates/objects/object_create.html.tera b/yopa-web/resources/templates/objects/object_create.html.tera index a403399..8e86fd1 100644 --- a/yopa-web/resources/templates/objects/object_create.html.tera +++ b/yopa-web/resources/templates/objects/object_create.html.tera @@ -14,7 +14,6 @@ Create {{model.name}} diff --git a/yopa-web/resources/templates/objects/object_update.html.tera b/yopa-web/resources/templates/objects/object_update.html.tera new file mode 100644 index 0000000..014ee41 --- /dev/null +++ b/yopa-web/resources/templates/objects/object_update.html.tera @@ -0,0 +1,21 @@ +{% extends "_layout" %} + +{% block title -%} +Edit {{object.name}} +{%- endblock %} + +{% block nav -%} +Home +{%- endblock %} + +{% block content -%} + +
+ + + +{%- endblock %} diff --git a/yopa-web/src/main.rs b/yopa-web/src/main.rs index 3caede1..ba98d27 100644 --- a/yopa-web/src/main.rs +++ b/yopa-web/src/main.rs @@ -157,6 +157,7 @@ async fn main() -> std::io::Result<()> { .service(routes::objects::create_form) .service(routes::objects::create) .service(routes::objects::detail) + .service(routes::objects::update_form) // .service(static_files) .default_service(web::to(|| HttpResponse::NotFound().body("File or endpoint not found"))) diff --git a/yopa-web/src/routes/objects.rs b/yopa-web/src/routes/objects.rs index ab5f261..3f1dbb0 100644 --- a/yopa-web/src/routes/objects.rs +++ b/yopa-web/src/routes/objects.rs @@ -4,7 +4,7 @@ use crate::session_ext::SessionExt; use crate::routes::models::object::ObjectModelForm; use crate::TERA; use crate::tera_ext::TeraExt; -use yopa::{ID, model}; +use yopa::{ID, model, Storage, data}; use yopa::data::Object; use serde::{Serialize,Deserialize}; use yopa::insert::InsertObj; @@ -14,6 +14,8 @@ use serde_json::Value; use itertools::Itertools; use yopa::model::{ObjectModel, PropertyModel, RelationModel}; use heck::TitleCase; +use std::collections::HashMap; +use json_dotpath::DotPaths; // we only need references here, Context serializes everything to Value. // cloning would be a waste of cycles @@ -48,6 +50,17 @@ pub(crate) async fn create_form( context.insert("model", model); + let form_data = prepare_object_create_data(&rg, model.id)?; + + context.insert("form_data", &form_data); + + TERA.build_response("objects/object_create", &context) +} + +fn prepare_object_create_data(rg : &Storage, model_id : ID) -> actix_web::Result { + let model = rg.get_object_model(model_id) + .ok_or_else(|| actix_web::error::ErrorNotFound("No such model"))?; + let relations : Vec<_> = rg.get_relation_models_for_object_model(model.id).collect(); let mut prop_object_ids : Vec = relations.iter().map(|r| r.id).collect(); @@ -61,19 +74,15 @@ pub(crate) async fn create_form( related_ids.sort(); related_ids.dedup(); - let form_data = ObjectCreateData { + Ok(ObjectCreateData { model_id: model.id, schema: Schema { obj_models: rg.get_object_models().collect(), // TODO get only the ones that matter here rel_models: relations, - prop_models: rg.get_property_models_for_parents(&prop_object_ids).collect() + prop_models: rg.get_property_models_for_parents(prop_object_ids).collect() }, - objects: rg.get_objects_of_type(&related_ids).collect() - }; - - context.insert("form_data", &form_data); - - TERA.build_response("objects/object_create", &context) + objects: rg.get_objects_of_type(related_ids).collect() + }) } #[post("/object/create")] @@ -189,7 +198,7 @@ pub(crate) async fn detail( let mut ids_to_get_values_for = relations.iter().map(|r| r.id).collect_vec(); ids_to_get_values_for.push(object_id); let mut grouped_values = { - rg.get_grouped_values_for_objects(&ids_to_get_values_for) + rg.get_grouped_values_for_objects(ids_to_get_values_for) }; // object's own properties @@ -252,3 +261,120 @@ pub(crate) async fn detail( TERA.build_response("objects/object_detail", &context) } + +#[derive(Serialize,Debug,Clone)] +struct EnrichedObject<'a> { + id: ID, + model: ID, + name: String, + values: HashMap>, + relations: HashMap>>, +} + +#[derive(Serialize,Debug,Clone)] +struct EnrichedRelation<'a> { + id: ID, + object: ID, + model: ID, + related: ID, + values: HashMap>, +} + +// FIXME relation values are now showing in the edit form! +// TODO save handling + +#[get("/object/update/{id}")] +pub(crate) async fn update_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; + + let object = rg.get_object(*id) + .ok_or_else(|| actix_web::error::ErrorNotFound("No such object"))?; + + let model = rg.get_object_model(object.model) + .ok_or_else(|| actix_web::error::ErrorNotFound("Object has no model"))?; + + // maybe its useful,idk + context.insert("model", &model); + context.insert("object", &object); + + let create_data = prepare_object_create_data(&rg, model.id)?; + + let mut value_map = HashMap::new(); + let mut relation_map = HashMap::new(); + + // Some properties may have no values, so we first check what IDs to expect + let prop_ids = create_data.schema.prop_models.iter() + .filter(|p| p.object == model.id).map(|p| p.id).collect_vec(); + + let mut values_grouped = rg.get_values_for_object(*id) + .into_group_map_by(|v| v.model); + + prop_ids.into_iter().for_each(|id| { + value_map.insert(id.to_string(), values_grouped.remove(&id).unwrap_or_default()); + }); + + { + // Some properties may have no values, so we first check what IDs to expect + let rel_ids = create_data.schema.rel_models.iter() + .filter(|p| p.object == model.id).map(|p| p.id).collect_vec(); + + let relations = rg.get_relations_for_object(*id).collect_vec(); + let grouped_relations = relations.iter() + .into_group_map_by(|relation| relation.model); + + let mut relation_properties = rg.get_grouped_prop_models_for_parents(rel_ids.clone()); + + let mut relation_values = rg.get_grouped_values_for_objects(rel_ids.clone()); + + for (rel_model_id, relations) in grouped_relations { + let mut instances = vec![]; + let props_for_rel = relation_properties.remove(&rel_model_id).unwrap_or_default(); + + for rel in relations { + let mut relation_values_map = HashMap::new(); + + // values keyed by model + let mut rel_values = relation_values.remove(&rel.id).unwrap_or_default().into_iter() + .into_group_map_by(|relation| relation.model); + + props_for_rel.iter().for_each(|prop_model| { + relation_values_map.insert(prop_model.id.to_string(), rel_values.remove(&prop_model.id).unwrap_or_default()); + }); + + let enriched = EnrichedRelation { + id: rel.id, + object: rel.object, + model: rel.model, + related: rel.related, + values: relation_values_map + }; + + instances.push(enriched); + } + + relation_map.insert(rel_model_id.to_string(), instances); + } + } + + let mut form = serde_json::to_value(create_data)?; + + let object = EnrichedObject { + id: object.id , + model: object.model, + name: object.name.clone(), + values: value_map, + relations: relation_map + }; + + form.dot_set("object", object); + context.insert("form_data", &form); + + TERA.build_response("objects/object_update", &context) +} diff --git a/yopa/src/lib.rs b/yopa/src/lib.rs index 4750986..92ea26d 100644 --- a/yopa/src/lib.rs +++ b/yopa/src/lib.rs @@ -368,7 +368,7 @@ impl Storage { .into_group_map_by(|model| model.object) } - pub fn get_grouped_prop_models_for_parents<'p, 'a : 'p>(&'a self, parents: &'p [ID]) -> HashMap> { + pub fn get_grouped_prop_models_for_parents(&self, parents: Vec) -> HashMap> { self.prop_models.values() .filter(|p| parents.contains(&p.object)) .into_group_map_by(|model| model.object) @@ -384,7 +384,7 @@ impl Storage { .filter(move |prop| prop.object == object_id) } - pub fn get_grouped_values_for_objects<'p, 'a : 'p>(&'a self, parents: &'p [ID]) -> HashMap> { + pub fn get_grouped_values_for_objects(&self, parents: Vec) -> HashMap> { self.properties.values() .filter(move |prop| parents.contains(&prop.object)) .into_group_map_by(|model| model.object) @@ -395,12 +395,12 @@ impl Storage { .filter(move |model| model.object == model_id) } - pub fn get_property_models_for_parents<'p, 'a : 'p>(&'a self, parents: &'p [ID]) -> impl Iterator { + pub fn get_property_models_for_parents(&self, parents: Vec) -> impl Iterator { self.prop_models.values() .filter(move |model| parents.contains(&model.object)) } - pub fn get_objects_of_type<'p, 'a : 'p>(&'a self, model_ids : &'p [ID]) -> impl Iterator { + pub fn get_objects_of_type(&self, model_ids : Vec) -> impl Iterator { self.objects.values() .filter(move |object| model_ids.contains(&object.model)) }