|
|
|
@ -1,71 +1,27 @@ |
|
|
|
|
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::fmt::{Debug, Display}; |
|
|
|
|
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<RelationModelDisplay<'a>>, |
|
|
|
|
reciprocal_relations: Vec<RelationModelDisplay<'a>>, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[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<HttpResponse> { |
|
|
|
|
Ok(HttpResponse::SeeOther() |
|
|
|
|
.header("location", path) // back - to where?
|
|
|
|
|
.finish()) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
trait ParseOrBadReq { |
|
|
|
|
fn parse_or_bad_request<T, E>(&self) -> actix_web::Result<T> |
|
|
|
|
where T: FromStr<Err=E>, |
|
|
|
|
E: Display + Debug + 'static; |
|
|
|
|
} |
|
|
|
|
use actix_session::Session; |
|
|
|
|
use actix_web::{HttpRequest, HttpResponse, Responder, web}; |
|
|
|
|
use actix_web::http::header::IntoHeaderValue; |
|
|
|
|
use serde::{Deserialize, Serialize}; |
|
|
|
|
|
|
|
|
|
impl ParseOrBadReq for &str { |
|
|
|
|
fn parse_or_bad_request<T, E>(&self) -> actix_web::Result<T> |
|
|
|
|
where T: FromStr<Err=E>, |
|
|
|
|
E: Display + Debug + 'static |
|
|
|
|
{ |
|
|
|
|
self.parse::<T>() |
|
|
|
|
.map_err(|e| { |
|
|
|
|
error!("Parse error for \"{}\"", self); |
|
|
|
|
actix_web::error::ErrorBadRequest(e) |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
use yopa::{DataType, ID, Storage, StorageError, TypedValue}; |
|
|
|
|
use yopa::model::{ObjectModel, PropertyModel, RelationModel}; |
|
|
|
|
|
|
|
|
|
impl ParseOrBadReq for String { |
|
|
|
|
fn parse_or_bad_request<T, E>(&self) -> actix_web::Result<T> |
|
|
|
|
where T: FromStr<Err=E>, |
|
|
|
|
E: Display + Debug + 'static |
|
|
|
|
{ |
|
|
|
|
self.as_str() |
|
|
|
|
.parse_or_bad_request() |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
use crate::session_ext::SessionExt; |
|
|
|
|
use crate::TERA; |
|
|
|
|
use crate::tera_ext::TeraExt; |
|
|
|
|
use crate::routes::relation_model::RelationModelDisplay; |
|
|
|
|
use crate::routes::object_model::ObjectModelDisplay; |
|
|
|
|
|
|
|
|
|
pub(crate) mod object_model; |
|
|
|
|
pub(crate) mod relation_model; |
|
|
|
|
pub(crate) mod property_model; |
|
|
|
|
|
|
|
|
|
#[get("/")] |
|
|
|
|
pub(crate) async fn index(session : Session, store : crate::YopaStoreWrapper) -> actix_web::Result<impl Responder> { |
|
|
|
|
|
|
|
|
|
pub(crate) async fn index(session: Session, store: crate::YopaStoreWrapper) -> actix_web::Result<impl Responder> { |
|
|
|
|
let rg = store.read().await; |
|
|
|
|
|
|
|
|
|
let models_iter = rg.get_object_models(); |
|
|
|
@ -86,7 +42,7 @@ pub(crate) async fn index(session : Session, store : crate::YopaStoreWrapper) -> |
|
|
|
|
RelationModelDisplay { |
|
|
|
|
model: rm, |
|
|
|
|
related_name: rg.get_model_name(rm.related), |
|
|
|
|
properties: rprops |
|
|
|
|
properties: rprops, |
|
|
|
|
} |
|
|
|
|
}).collect::<Vec<_>>(); |
|
|
|
|
relations.sort_by_key(|d| &d.model.name); |
|
|
|
@ -100,7 +56,7 @@ pub(crate) async fn index(session : Session, store : crate::YopaStoreWrapper) -> |
|
|
|
|
RelationModelDisplay { |
|
|
|
|
model: rm, |
|
|
|
|
related_name: rg.get_model_name(rm.object), |
|
|
|
|
properties: rprops |
|
|
|
|
properties: rprops, |
|
|
|
|
} |
|
|
|
|
}).collect::<Vec<_>>(); |
|
|
|
|
reciprocal_relations.sort_by_key(|d| &d.model.reciprocal_name); |
|
|
|
@ -113,7 +69,7 @@ pub(crate) async fn index(session : Session, store : crate::YopaStoreWrapper) -> |
|
|
|
|
name: &om.name, |
|
|
|
|
properties, |
|
|
|
|
relations, |
|
|
|
|
reciprocal_relations |
|
|
|
|
reciprocal_relations, |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -125,311 +81,3 @@ pub(crate) async fn index(session : Session, store : crate::YopaStoreWrapper) -> |
|
|
|
|
|
|
|
|
|
TERA.build_response("index", &ctx) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[get("/model/object/create")] |
|
|
|
|
pub(crate) async fn object_model_create_form(session : Session) -> actix_web::Result<impl Responder> { |
|
|
|
|
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<ObjectModelCreate>, |
|
|
|
|
store : crate::YopaStoreWrapper, |
|
|
|
|
session : Session |
|
|
|
|
) -> actix_web::Result<impl Responder> { |
|
|
|
|
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<String>, |
|
|
|
|
store : crate::YopaStoreWrapper, |
|
|
|
|
session : Session |
|
|
|
|
) -> actix_web::Result<impl Responder> { |
|
|
|
|
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<i32>, |
|
|
|
|
pub multiple : Option<i32>, |
|
|
|
|
pub related : ID, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[post("/model/relation/create")] |
|
|
|
|
pub(crate) async fn relation_model_create( |
|
|
|
|
form : web::Form<RelationModelCreate>, |
|
|
|
|
store : crate::YopaStoreWrapper, |
|
|
|
|
session : Session |
|
|
|
|
) -> actix_web::Result<impl Responder> { |
|
|
|
|
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<String>, |
|
|
|
|
store : crate::YopaStoreWrapper, |
|
|
|
|
session : Session |
|
|
|
|
) -> actix_web::Result<impl Responder> { |
|
|
|
|
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<i32>, |
|
|
|
|
pub multiple : Option<i32>, |
|
|
|
|
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<PropertyModelCreate>, |
|
|
|
|
store : crate::YopaStoreWrapper, |
|
|
|
|
session : Session |
|
|
|
|
) -> actix_web::Result<impl Responder> { |
|
|
|
|
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<String>, |
|
|
|
|
store : crate::YopaStoreWrapper, |
|
|
|
|
session : Session |
|
|
|
|
) -> actix_web::Result<impl Responder> { |
|
|
|
|
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<String>, |
|
|
|
|
store : crate::YopaStoreWrapper, |
|
|
|
|
session : Session |
|
|
|
|
) -> actix_web::Result<impl Responder> { |
|
|
|
|
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<String>, |
|
|
|
|
store : crate::YopaStoreWrapper, |
|
|
|
|
session : Session |
|
|
|
|
) -> actix_web::Result<impl Responder> { |
|
|
|
|
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?
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|