diff --git a/yopa-web/resources/templates/relation_create.html.tera b/yopa-web/resources/templates/relation_create.html.tera new file mode 100644 index 0000000..f1e2c90 --- /dev/null +++ b/yopa-web/resources/templates/relation_create.html.tera @@ -0,0 +1,39 @@ +{% extends "_layout" %} + +{% block title -%} +Define relation +{%- endblock %} + +{% block nav -%} +Home +{%- endblock %} + +{% block content -%} + +

Define new relation from "{{object.name}}"

+ +
+ + + +
+ + + +
+ + +
+ + +
+ + +
+ + +{%- endblock %} diff --git a/yopa-web/src/main.rs b/yopa-web/src/main.rs index 5f6f373..443e972 100644 --- a/yopa-web/src/main.rs +++ b/yopa-web/src/main.rs @@ -97,6 +97,8 @@ async fn main() -> std::io::Result<()> { .service(routes::index) .service(routes::object_model_create_form) .service(routes::object_model_create) + .service(routes::relation_model_create_form) + .service(routes::relation_model_create) .service(routes::object_model_delete) .service(routes::relation_model_delete) .service(routes::property_model_delete) @@ -107,6 +109,7 @@ async fn main() -> std::io::Result<()> { .run().await } + fn init_yopa() -> YopaStoreWrapper { let mut store = Storage::new(); diff --git a/yopa-web/src/routes.rs b/yopa-web/src/routes.rs index c26630f..f5b54f6 100644 --- a/yopa-web/src/routes.rs +++ b/yopa-web/src/routes.rs @@ -1,12 +1,15 @@ use actix_web::{web, HttpRequest, Responder, HttpResponse}; use crate::TERA; use crate::tera_ext::TeraExt; -use yopa::{Storage, StorageError}; +use yopa::{Storage, StorageError, ID}; 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> { @@ -23,12 +26,39 @@ struct RelationModelDisplay<'a> { properties: Vec<&'a PropertyModel>, } -fn redirect(path : impl AsRef) -> actix_web::Result { +fn redirect(path : impl IntoHeaderValue) -> actix_web::Result { Ok(HttpResponse::SeeOther() - .header("location", path.as_ref()) // back - to where? + .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| 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 { @@ -115,6 +145,70 @@ pub(crate) async fn object_model_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 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(), + 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)) + } + } +} + #[get("/model/object/delete/{id}")] pub(crate) async fn object_model_delete( id : web::Path, @@ -143,7 +237,7 @@ pub(crate) async fn relation_model_delete( session : Session ) -> actix_web::Result { let mut wg = store.write().await; - match wg.undefine_relation(id.parse().map_err(|e| actix_web::error::ErrorBadRequest(e))?) { + 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)); @@ -164,7 +258,7 @@ pub(crate) async fn property_model_delete( session : Session ) -> actix_web::Result { let mut wg = store.write().await; - match wg.undefine_property(id.parse().map_err(|e| actix_web::error::ErrorBadRequest(e))?) { + 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)); diff --git a/yopa/src/lib.rs b/yopa/src/lib.rs index f298d55..c8a5857 100644 --- a/yopa/src/lib.rs +++ b/yopa/src/lib.rs @@ -339,6 +339,18 @@ impl Storage { self.obj_models.values() } + pub fn get_object_model(&self, id : ID) -> Option<&ObjectModel> { + self.obj_models.get(&id) + } + + pub fn get_relation_model(&self, id : ID) -> Option<&RelationModel> { + self.rel_models.get(&id) + } + + pub fn get_prop_model(&self, id : ID) -> Option<&PropertyModel> { + self.prop_models.get(&id) + } + pub fn get_grouped_prop_models(&self) -> HashMap> { self.prop_models.values() .into_group_map_by(|model| model.object)