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)