|
|
|
use actix_session::Session;
|
|
|
|
use actix_web::{web, HttpResponse, Responder};
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
|
|
|
use yopa::model::{ObjectModel, PropertyModel};
|
|
|
|
use yopa::ID;
|
|
|
|
|
|
|
|
use crate::routes::models::relation::RelationModelDisplay;
|
|
|
|
use crate::session_ext::SessionExt;
|
|
|
|
use crate::tera_ext::TeraExt;
|
|
|
|
use crate::utils::{redirect, StorageErrorIntoResponseError};
|
|
|
|
use crate::TERA;
|
|
|
|
use itertools::Itertools;
|
|
|
|
|
|
|
|
#[derive(Serialize, Debug)]
|
|
|
|
pub(crate) struct ObjectModelDisplay<'a> {
|
|
|
|
pub(crate) id: yopa::ID,
|
|
|
|
pub(crate) name: &'a str,
|
|
|
|
#[serde(skip)]
|
|
|
|
pub(crate) model : &'a ObjectModel,
|
|
|
|
pub(crate) properties: Vec<&'a PropertyModel>,
|
|
|
|
pub(crate) relations: Vec<RelationModelDisplay<'a>>,
|
|
|
|
pub(crate) reciprocal_relations: Vec<RelationModelDisplay<'a>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[get("/model/object/create")]
|
|
|
|
pub(crate) async fn create_form(session: Session) -> actix_web::Result<impl Responder> {
|
|
|
|
let mut context = tera::Context::new();
|
|
|
|
session.render_flash(&mut context);
|
|
|
|
|
|
|
|
// Re-fill old values
|
|
|
|
if let Ok(Some(form)) = session.take::<ObjectModelForm>("old") {
|
|
|
|
context.insert("old", &form);
|
|
|
|
} else {
|
|
|
|
context.insert("old", &ObjectModelForm::default());
|
|
|
|
}
|
|
|
|
|
|
|
|
TERA.build_response("models/model_create", &context)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
|
|
|
pub(crate) struct ObjectModelForm {
|
|
|
|
pub name: String,
|
|
|
|
#[serde(default)]
|
|
|
|
// #[serde(with="serde_with::rust::default_on_error")] // This is because "" can be selected
|
|
|
|
#[serde(with = "my_string_empty_as_none")]
|
|
|
|
pub name_property: Option<ID>,
|
|
|
|
#[serde(default)]
|
|
|
|
pub sort_key: i64,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[post("/model/object/create")]
|
|
|
|
pub(crate) async fn create(
|
|
|
|
form: web::Form<ObjectModelForm>,
|
|
|
|
store: crate::YopaStoreWrapper,
|
|
|
|
session: Session,
|
|
|
|
) -> actix_web::Result<HttpResponse> {
|
|
|
|
let mut wg = store.write().await;
|
|
|
|
let form = form.into_inner();
|
|
|
|
match wg.define_object(ObjectModel {
|
|
|
|
id: Default::default(),
|
|
|
|
name: form.name.clone(),
|
|
|
|
name_property: form.name_property,
|
|
|
|
sort_key: form.sort_key
|
|
|
|
}) {
|
|
|
|
Ok(_id) => {
|
|
|
|
wg.persist().err_to_500()?;
|
|
|
|
debug!("Object created, redirecting to root");
|
|
|
|
session.flash_success(format!("Object model \"{}\" created.", form.name));
|
|
|
|
redirect("/models")
|
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
warn!("Error creating model: {}", e);
|
|
|
|
session.flash_error(e.to_string());
|
|
|
|
session.set("old", form).unwrap();
|
|
|
|
redirect("/model/object/create")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[get("/model/object/update/{model_id}")]
|
|
|
|
pub(crate) async fn update_form(
|
|
|
|
model_id: web::Path<ID>,
|
|
|
|
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;
|
|
|
|
|
|
|
|
let model = rg
|
|
|
|
.get_object_model(*model_id)
|
|
|
|
.ok_or_else(|| actix_web::error::ErrorNotFound("No such model"))?;
|
|
|
|
|
|
|
|
// Re-fill old values
|
|
|
|
if let Ok(Some(form)) = session.take::<ObjectModelForm>("old") {
|
|
|
|
let mut model = model.clone();
|
|
|
|
context.insert("old", &form);
|
|
|
|
model.name = form.name;
|
|
|
|
model.name_property = form.name_property;
|
|
|
|
context.insert("model", &model);
|
|
|
|
} else {
|
|
|
|
context.insert("model", model);
|
|
|
|
context.insert(
|
|
|
|
"old",
|
|
|
|
&ObjectModelForm {
|
|
|
|
name: model.name.to_string(),
|
|
|
|
name_property: model.name_property,
|
|
|
|
sort_key: model.sort_key,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
let properties = rg.get_property_models_for_parent(*model_id).collect_vec();
|
|
|
|
|
|
|
|
context.insert("properties", &properties);
|
|
|
|
|
|
|
|
TERA.build_response("models/model_update", &context)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[post("/model/object/update/{model_id}")]
|
|
|
|
pub(crate) async fn update(
|
|
|
|
model_id: web::Path<ID>,
|
|
|
|
form: web::Form<ObjectModelForm>,
|
|
|
|
store: crate::YopaStoreWrapper,
|
|
|
|
session: Session,
|
|
|
|
) -> actix_web::Result<impl Responder> {
|
|
|
|
let mut wg = store.write().await;
|
|
|
|
let form = form.into_inner();
|
|
|
|
|
|
|
|
let id = model_id.into_inner();
|
|
|
|
match wg.update_object_model(ObjectModel {
|
|
|
|
id,
|
|
|
|
name: form.name.clone(),
|
|
|
|
name_property: form.name_property,
|
|
|
|
sort_key: form.sort_key,
|
|
|
|
}) {
|
|
|
|
Ok(_id) => {
|
|
|
|
wg.persist().err_to_500()?;
|
|
|
|
debug!("Object updated, redirecting to root");
|
|
|
|
session.flash_success(format!("Object model \"{}\" updated.", form.name));
|
|
|
|
redirect("/models")
|
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
warn!("Error updating model: {}", e);
|
|
|
|
session.flash_error(e.to_string());
|
|
|
|
session.set("old", form).unwrap();
|
|
|
|
redirect(format!("/model/object/update/{}", id))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[get("/model/object/delete/{id}")]
|
|
|
|
pub(crate) async fn delete(
|
|
|
|
id: web::Path<ID>,
|
|
|
|
store: crate::YopaStoreWrapper,
|
|
|
|
session: Session,
|
|
|
|
) -> actix_web::Result<impl Responder> {
|
|
|
|
let mut wg = store.write().await;
|
|
|
|
match wg.undefine_object(*id) {
|
|
|
|
Ok(om) => {
|
|
|
|
wg.persist().err_to_500()?;
|
|
|
|
debug!("Object model deleted, redirecting to root");
|
|
|
|
session.flash_success(format!("Object model \"{}\" deleted.", om.name));
|
|
|
|
redirect("/models")
|
|
|
|
}
|
|
|
|
Err(e) => {
|
|
|
|
warn!("Error deleting object model: {}", e);
|
|
|
|
session.flash_error(e.to_string());
|
|
|
|
redirect("/models") // back?
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub mod my_string_empty_as_none {
|
|
|
|
use serde::de::{Error, Visitor};
|
|
|
|
use serde::{Deserializer, Serialize, Serializer};
|
|
|
|
use std::fmt;
|
|
|
|
use std::fmt::Display;
|
|
|
|
use std::marker::PhantomData;
|
|
|
|
use std::str::FromStr;
|
|
|
|
use yopa::ID;
|
|
|
|
|
|
|
|
// FIXME largely copied from serde_with
|
|
|
|
|
|
|
|
/// Deserialize an `Option<T>` from a string using `FromStr`
|
|
|
|
pub fn deserialize<'de, D, S>(deserializer: D) -> Result<Option<S>, D::Error>
|
|
|
|
where
|
|
|
|
D: Deserializer<'de>,
|
|
|
|
S: FromStr,
|
|
|
|
S::Err: Display,
|
|
|
|
{
|
|
|
|
struct OptionStringEmptyNone<S>(PhantomData<S>);
|
|
|
|
impl<'de, S> Visitor<'de> for OptionStringEmptyNone<S>
|
|
|
|
where
|
|
|
|
S: FromStr,
|
|
|
|
S::Err: Display,
|
|
|
|
{
|
|
|
|
type Value = Option<S>;
|
|
|
|
|
|
|
|
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
formatter.write_str("any string")
|
|
|
|
}
|
|
|
|
|
|
|
|
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
|
|
|
|
where
|
|
|
|
E: Error,
|
|
|
|
{
|
|
|
|
match value {
|
|
|
|
"" => Ok(None),
|
|
|
|
v => S::from_str(v).map(Some).map_err(Error::custom),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
|
|
|
|
where
|
|
|
|
E: Error,
|
|
|
|
{
|
|
|
|
match &*value {
|
|
|
|
"" => Ok(None),
|
|
|
|
v => S::from_str(v).map(Some).map_err(Error::custom),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO remove?
|
|
|
|
fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
|
|
|
|
where
|
|
|
|
E: Error,
|
|
|
|
{
|
|
|
|
self.visit_str(&v.to_string())
|
|
|
|
}
|
|
|
|
|
|
|
|
// handles the `null` case
|
|
|
|
fn visit_unit<E>(self) -> Result<Self::Value, E>
|
|
|
|
where
|
|
|
|
E: Error,
|
|
|
|
{
|
|
|
|
Ok(None)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
deserializer.deserialize_any(OptionStringEmptyNone(PhantomData))
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Serialize a string from `Option<T>` using `AsRef<str>` or using the empty string if `None`.
|
|
|
|
pub fn serialize<S>(option: &Option<ID>, serializer: S) -> Result<S::Ok, S::Error>
|
|
|
|
where
|
|
|
|
S: Serializer,
|
|
|
|
{
|
|
|
|
option.serialize(serializer)
|
|
|
|
|
|
|
|
// if let Some(value) = option {
|
|
|
|
// value.serialize(serializer)
|
|
|
|
// } else {
|
|
|
|
// "".serialize(serializer)
|
|
|
|
// }
|
|
|
|
}
|
|
|
|
}
|