diff --git a/yopa-web/resources/templates/_macros.html.tera b/yopa-web/resources/templates/_macros.html.tera index 2ee00a7..53d5e13 100644 --- a/yopa-web/resources/templates/_macros.html.tera +++ b/yopa-web/resources/templates/_macros.html.tera @@ -5,5 +5,5 @@ {%- endif -%} {%- if prop.optional %}, OPTIONAL{% endif %} {%- if prop.multiple %}, MULTIPLE{% endif %} - Delete property + Delete property {% endmacro input %} diff --git a/yopa-web/resources/templates/index.html.tera b/yopa-web/resources/templates/index.html.tera index 5fb9989..0031272 100644 --- a/yopa-web/resources/templates/index.html.tera +++ b/yopa-web/resources/templates/index.html.tera @@ -23,7 +23,7 @@
  • {{model.name}}
    - Delete model · + Delete model · Edit model · New relation · New property @@ -50,7 +50,8 @@ {%- if rel.model.multiple %}, MULTIPLE{% endif %}
    - Delete relation · + Edit relation · + Delete relation · New property
    diff --git a/yopa-web/resources/templates/model_create.html.tera b/yopa-web/resources/templates/model_create.html.tera index afa1571..d1726e7 100644 --- a/yopa-web/resources/templates/model_create.html.tera +++ b/yopa-web/resources/templates/model_create.html.tera @@ -12,11 +12,9 @@ Define object

    Define new object model

    -{# TODO implement value restoration on error #} -
    -
    +
    diff --git a/yopa-web/resources/templates/property_create.html.tera b/yopa-web/resources/templates/property_create.html.tera index d441145..b66db34 100644 --- a/yopa-web/resources/templates/property_create.html.tera +++ b/yopa-web/resources/templates/property_create.html.tera @@ -22,11 +22,11 @@ Define property
    - +
    -
    +

    - +
    -
    +

    + + +
    + + + +
    + + +
    + +

    The related object cannot be changed. Create a new relation if needed.

    + + + + + +{%- endblock %} diff --git a/yopa-web/src/main.rs b/yopa-web/src/main.rs index c5311ad..aa52a4f 100644 --- a/yopa-web/src/main.rs +++ b/yopa-web/src/main.rs @@ -56,6 +56,21 @@ pub(crate) static TERA: Lazy = Lazy::new(|| { Err(tera::Error::msg("Expected nonenmpty object")) }); + tera.register_function("opt", |args: &HashMap| -> tera::Result { + if args.len() != 1 { + return Err("Expected 1 argument".into()); + } + match args.iter().nth(0) { + Some((name, &Value::Bool(true))) => { + Ok(Value::String(name.clone())) + }, + Some((_, &Value::Bool(false))) => { + Ok(Value::Null) + }, + _ => Err("Expected bool argument".into()), + } + }); + // TODO need to inject HttpRequest::url_for() into tera context, but it then can't be accessed by the functions. // tera.register_function("url_for", |args: HashMap| -> tera::Result { // match args.get("name") { @@ -110,6 +125,8 @@ async fn main() -> std::io::Result<()> { // .service(routes::relation_model::create_form) .service(routes::relation_model::create) + .service(routes::relation_model::update_form) + .service(routes::relation_model::update) .service(routes::relation_model::delete) // .service(routes::property_model::create_form) diff --git a/yopa-web/src/routes/object_model.rs b/yopa-web/src/routes/object_model.rs index 44f3ea0..ccb0940 100644 --- a/yopa-web/src/routes/object_model.rs +++ b/yopa-web/src/routes/object_model.rs @@ -25,10 +25,17 @@ pub(crate) async fn create_form(session: Session) -> actix_web::Result("old") { + context.insert("old", &form); + } else { + context.insert("old", &ObjectModelForm::default()); + } + TERA.build_response("model_create", &context) } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Default, Debug, Clone, Serialize, Deserialize)] pub(crate) struct ObjectModelForm { pub name: String, } @@ -53,6 +60,7 @@ pub(crate) async fn create( Err(e) => { warn!("Error creating model: {:?}", e); session.flash_error(e.to_string()); + session.set("old", form); redirect("/model/object/create") } } diff --git a/yopa-web/src/routes/property_model.rs b/yopa-web/src/routes/property_model.rs index cad601d..673a740 100644 --- a/yopa-web/src/routes/property_model.rs +++ b/yopa-web/src/routes/property_model.rs @@ -50,8 +50,10 @@ pub(crate) async fn create_form( pub(crate) struct PropertyModelCreate { pub object: ID, pub name: String, - pub optional: Option, - pub multiple: Option, + #[serde(default)] + pub optional: bool, + #[serde(default)] + pub multiple: bool, pub data_type: DataType, /// Default value to be parsed to the data type /// May be unused if empty and optional @@ -67,8 +69,8 @@ pub(crate) async fn create( 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; + let optional = form.optional; + let multiple = form.multiple; match wg.define_property(PropertyModel { id: Default::default(), diff --git a/yopa-web/src/routes/relation_model.rs b/yopa-web/src/routes/relation_model.rs index c690e45..f9d3846 100644 --- a/yopa-web/src/routes/relation_model.rs +++ b/yopa-web/src/routes/relation_model.rs @@ -44,18 +44,20 @@ pub(crate) async fn create_form( } #[derive(Deserialize)] -pub(crate) struct RelationModelCreate { +pub(crate) struct RelationModelCreateForm { pub object: ID, pub name: String, pub reciprocal_name: String, - pub optional: Option, - pub multiple: Option, + #[serde(default)] + pub optional: bool, + #[serde(default)] + pub multiple: bool, pub related: ID, } #[post("/model/relation/create")] pub(crate) async fn create( - form: web::Form, + form: web::Form, store: crate::YopaStoreWrapper, session: Session, ) -> actix_web::Result { @@ -66,8 +68,8 @@ pub(crate) async fn create( 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, + optional: form.optional, + multiple: form.multiple, related: form.related, }) { Ok(_id) => { @@ -109,3 +111,74 @@ pub(crate) async fn delete( } } } + +#[derive(Serialize,Deserialize)] +pub(crate) struct RelationModelEditForm { + pub name: String, + pub reciprocal_name: String, + #[serde(default)] + pub optional: bool, + #[serde(default)] + pub multiple: bool, +} + +#[get("/model/relation/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_relation_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.optional = form.optional; // TODO try if regular bools cant be used + model.multiple = form.multiple; + context.insert("model", &model); + } else { + context.insert("model", model); + } + + TERA.build_response("relation_update", &context) +} + +#[post("/model/relation/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_relation(RelationModel { + id, + object: Default::default(), // dummy + name: form.name.clone(), + reciprocal_name: form.reciprocal_name.to_string(), + optional: form.optional, + multiple: form.multiple, + related: Default::default() // dummy + }) { + Ok(_id) => { + debug!("Relation updated, redirecting to root"); + session.flash_success(format!("Relation model \"{}\" updated.", form.name)); + redirect("/") + } + Err(e) => { + warn!("Error updating model: {:?}", e); + session.flash_error(e.to_string()); + session.set("old", form); + redirect(format!("/model/relation/update/{}", id)) + } + } +} diff --git a/yopa/src/lib.rs b/yopa/src/lib.rs index 75f2e31..19c4d81 100644 --- a/yopa/src/lib.rs +++ b/yopa/src/lib.rs @@ -389,4 +389,33 @@ impl Storage { self.obj_models.insert(model.id, model); Ok(()) } + + pub fn update_relation(&mut self, mut rel : RelationModel) -> Result<(), StorageError> { + // Object and Related can't be changed, so we re-fill them from the existing model + if let Some(existing) = self.rel_models.get(&rel.id) { + rel.object = existing.object; + rel.related = existing.related; + } else { + return Err(StorageError::NotExist(format!("Relation model ID {} does not exist.", rel.id).into())); + } + + // Difficult checks ... + + // yes this is stupid and inefficient and slow and + if let Some((_, colliding)) = self.rel_models.iter().find(|(_, other)| { + (other.name == rel.name && other.object == rel.object && rel.id != other.id) // Exact match + || (other.name == rel.reciprocal_name && other.object == rel.related && rel.id != other.id) // Our reciprocal name collides with related's own relation name + || (other.reciprocal_name == rel.name && other.related == rel.object && rel.id != other.id) // Our name name collides with a reciprocal name on the other relation + || (other.reciprocal_name == rel.reciprocal_name && other.related == rel.related && rel.id != other.id) // Reciprocal names collide for the same destination + }) { + return Err(StorageError::ConstraintViolation( + format!("name collision (\"{}\" / \"{}\") with existing relation (\"{}\" / \"{}\")", + rel.name, rel.reciprocal_name, + colliding.name, colliding.reciprocal_name + ).into())); + } + + self.rel_models.insert(rel.id, rel); + Ok(()) + } }