|
|
@ -1,22 +1,24 @@ |
|
|
|
use crate::session_ext::SessionExt; |
|
|
|
use std::borrow::{Borrow, Cow}; |
|
|
|
use actix_session::Session; |
|
|
|
use std::collections::HashMap; |
|
|
|
use actix_web::{web, HttpResponse, Responder}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
use crate::tera_ext::TeraExt; |
|
|
|
|
|
|
|
use crate::utils::{redirect, StorageErrorIntoResponseError}; |
|
|
|
|
|
|
|
use crate::TERA; |
|
|
|
|
|
|
|
use serde::Serialize; |
|
|
|
|
|
|
|
use yopa::data::Object; |
|
|
|
|
|
|
|
use yopa::insert::InsertObj; |
|
|
|
|
|
|
|
use yopa::{data, model, Storage, ID}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
use actix_session::Session; |
|
|
|
|
|
|
|
use actix_web::{HttpResponse, Responder, web}; |
|
|
|
use heck::TitleCase; |
|
|
|
use heck::TitleCase; |
|
|
|
use itertools::Itertools; |
|
|
|
use itertools::Itertools; |
|
|
|
use json_dotpath::DotPaths; |
|
|
|
use json_dotpath::DotPaths; |
|
|
|
use std::collections::HashMap; |
|
|
|
use serde::Serialize; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
use yopa::{data, ID, model, Storage}; |
|
|
|
|
|
|
|
use yopa::data::Object; |
|
|
|
|
|
|
|
use yopa::insert::InsertObj; |
|
|
|
use yopa::model::{ObjectModel, PropertyModel, RelationModel}; |
|
|
|
use yopa::model::{ObjectModel, PropertyModel, RelationModel}; |
|
|
|
use yopa::update::UpdateObj; |
|
|
|
use yopa::update::UpdateObj; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
use crate::session_ext::SessionExt; |
|
|
|
|
|
|
|
use crate::TERA; |
|
|
|
|
|
|
|
use crate::tera_ext::TeraExt; |
|
|
|
|
|
|
|
use crate::utils::{redirect, StorageErrorIntoResponseError}; |
|
|
|
|
|
|
|
|
|
|
|
// we only need references here, Context serializes everything to Value.
|
|
|
|
// we only need references here, Context serializes everything to Value.
|
|
|
|
// cloning would be a waste of cycles
|
|
|
|
// cloning would be a waste of cycles
|
|
|
|
|
|
|
|
|
|
|
@ -27,11 +29,18 @@ pub struct Schema<'a> { |
|
|
|
pub prop_models: Vec<&'a model::PropertyModel>, |
|
|
|
pub prop_models: Vec<&'a model::PropertyModel>, |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Serialize)] |
|
|
|
|
|
|
|
pub struct ObjectDisplay<'a> { |
|
|
|
|
|
|
|
pub id: ID, |
|
|
|
|
|
|
|
pub model: ID, |
|
|
|
|
|
|
|
pub name: Cow<'a, str>, |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
#[derive(Serialize, Debug, Clone)] |
|
|
|
#[derive(Serialize, Debug, Clone)] |
|
|
|
pub struct ObjectCreateData<'a> { |
|
|
|
pub struct ObjectCreateData<'a> { |
|
|
|
pub model_id: ID, |
|
|
|
pub model_id: ID, |
|
|
|
pub schema: Schema<'a>, |
|
|
|
pub schema: Schema<'a>, |
|
|
|
pub objects: Vec<&'a Object>, |
|
|
|
pub objects: Vec<ObjectDisplay<'a>>, |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
#[get("/object/create/{model_id}")] |
|
|
|
#[get("/object/create/{model_id}")] |
|
|
@ -76,16 +85,25 @@ fn prepare_object_create_data(rg: &Storage, model_id: ID) -> actix_web::Result<O |
|
|
|
related_ids.sort(); |
|
|
|
related_ids.sort(); |
|
|
|
related_ids.dedup(); |
|
|
|
related_ids.dedup(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let mut models_to_fetch = vec![model_id]; |
|
|
|
|
|
|
|
models_to_fetch.extend(&related_ids); |
|
|
|
|
|
|
|
|
|
|
|
Ok(ObjectCreateData { |
|
|
|
Ok(ObjectCreateData { |
|
|
|
model_id: model.id, |
|
|
|
model_id: model.id, |
|
|
|
schema: Schema { |
|
|
|
schema: Schema { |
|
|
|
obj_models: rg.get_object_models().collect(), // TODO get only the ones that matter here
|
|
|
|
obj_models: rg.get_object_models_by_ids(models_to_fetch).collect(), // TODO get only the ones that matter here
|
|
|
|
rel_models: relations, |
|
|
|
rel_models: relations, |
|
|
|
prop_models: rg |
|
|
|
prop_models: rg |
|
|
|
.get_property_models_for_parents(prop_object_ids) |
|
|
|
.get_property_models_for_parents(prop_object_ids) |
|
|
|
.collect(), |
|
|
|
.collect(), |
|
|
|
}, |
|
|
|
}, |
|
|
|
objects: rg.get_objects_of_types(related_ids).collect(), |
|
|
|
objects: rg.get_objects_of_types(related_ids).map(|o| { |
|
|
|
|
|
|
|
ObjectDisplay { |
|
|
|
|
|
|
|
id: o.id, |
|
|
|
|
|
|
|
model: o.model, |
|
|
|
|
|
|
|
name: rg.get_object_name(o), |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}).collect(), |
|
|
|
}) |
|
|
|
}) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -97,22 +115,17 @@ pub(crate) async fn create( |
|
|
|
) -> actix_web::Result<impl Responder> { |
|
|
|
) -> actix_web::Result<impl Responder> { |
|
|
|
warn!("{:?}", form); |
|
|
|
warn!("{:?}", form); |
|
|
|
|
|
|
|
|
|
|
|
// let des : InsertObj = serde_json::from_value(form.into_inner()).unwrap();
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
// Ok(HttpResponse::Ok().finish())
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let mut wg = store.write().await; |
|
|
|
let mut wg = store.write().await; |
|
|
|
let form = form.into_inner(); |
|
|
|
let form = form.into_inner(); |
|
|
|
let name = form.name.clone(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let model_name = wg.get_model_name(form.model).to_owned().to_title_case(); |
|
|
|
let model_name = wg.get_model_name(form.model).to_owned().to_title_case(); |
|
|
|
|
|
|
|
|
|
|
|
match wg.insert_object(form) { |
|
|
|
match wg.insert_object(form) { |
|
|
|
Ok(_id) => { |
|
|
|
Ok(id) => { |
|
|
|
wg.persist().err_to_500()?; |
|
|
|
wg.persist().err_to_500()?; |
|
|
|
|
|
|
|
let obj_name = wg.get_object_name_by_id(id); |
|
|
|
debug!("Object created, redirecting to root"); |
|
|
|
debug!("Object created, redirecting to root"); |
|
|
|
session.flash_success(format!("{} \"{}\" created.", model_name, name)); |
|
|
|
session.flash_success(format!("{} \"{}\" created.", model_name, obj_name)); |
|
|
|
Ok(HttpResponse::Ok().finish()) |
|
|
|
Ok(HttpResponse::Ok().finish()) |
|
|
|
} |
|
|
|
} |
|
|
|
Err(e) => { |
|
|
|
Err(e) => { |
|
|
@ -125,7 +138,7 @@ pub(crate) async fn create( |
|
|
|
#[derive(Debug, Serialize, Clone)] |
|
|
|
#[derive(Debug, Serialize, Clone)] |
|
|
|
struct ModelWithObjects<'a> { |
|
|
|
struct ModelWithObjects<'a> { |
|
|
|
model: &'a ObjectModel, |
|
|
|
model: &'a ObjectModel, |
|
|
|
objects: Vec<&'a Object>, |
|
|
|
objects: Vec<ObjectDisplay<'a>>, |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
#[get("/objects")] |
|
|
|
#[get("/objects")] |
|
|
@ -148,8 +161,16 @@ pub(crate) async fn list_inner( |
|
|
|
.get_object_models() |
|
|
|
.get_object_models() |
|
|
|
.sorted_by_key(|m| &m.name) |
|
|
|
.sorted_by_key(|m| &m.name) |
|
|
|
.map(|model| { |
|
|
|
.map(|model| { |
|
|
|
let mut objects = objects_by_model.remove(&model.id).unwrap_or_default(); |
|
|
|
let objects = objects_by_model.remove(&model.id).unwrap_or_default(); |
|
|
|
objects.sort_by_key(|o| &o.name); |
|
|
|
let mut objects = objects.into_iter().map(|o| { |
|
|
|
|
|
|
|
ObjectDisplay { |
|
|
|
|
|
|
|
id: o.id, |
|
|
|
|
|
|
|
model: o.model, |
|
|
|
|
|
|
|
name: rg.get_object_name(o), // TODO optimize
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
}).collect_vec(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
objects.sort_by(|a, b| a.name.cmp(&b.name)); |
|
|
|
ModelWithObjects { model, objects } |
|
|
|
ModelWithObjects { model, objects } |
|
|
|
}) |
|
|
|
}) |
|
|
|
.collect(); |
|
|
|
.collect(); |
|
|
@ -176,7 +197,8 @@ struct RelationView<'a> { |
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Serialize)] |
|
|
|
#[derive(Debug, Serialize)] |
|
|
|
struct RelationInstanceView<'a> { |
|
|
|
struct RelationInstanceView<'a> { |
|
|
|
related: &'a Object, |
|
|
|
related: ObjectDisplay<'a>, |
|
|
|
|
|
|
|
related_name: Cow<'a, str>, |
|
|
|
properties: Vec<PropertyView<'a>>, |
|
|
|
properties: Vec<PropertyView<'a>>, |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -201,7 +223,11 @@ pub(crate) async fn detail( |
|
|
|
.get_object_model(object.model) |
|
|
|
.get_object_model(object.model) |
|
|
|
.ok_or_else(|| actix_web::error::ErrorNotFound("No such model"))?; |
|
|
|
.ok_or_else(|| actix_web::error::ErrorNotFound("No such model"))?; |
|
|
|
|
|
|
|
|
|
|
|
context.insert("object", object); |
|
|
|
context.insert("object", &ObjectDisplay { |
|
|
|
|
|
|
|
id: object_id, |
|
|
|
|
|
|
|
model: object.model, |
|
|
|
|
|
|
|
name: rg.get_object_name(object), |
|
|
|
|
|
|
|
}); |
|
|
|
context.insert("model", model); |
|
|
|
context.insert("model", model); |
|
|
|
context.insert("kind", &rg.get_model_name(object.model)); |
|
|
|
context.insert("kind", &rg.get_model_name(object.model)); |
|
|
|
|
|
|
|
|
|
|
@ -270,13 +296,20 @@ pub(crate) async fn detail( |
|
|
|
|
|
|
|
|
|
|
|
view_rel_properties.sort_by_key(|p| &p.model.name); |
|
|
|
view_rel_properties.sort_by_key(|p| &p.model.name); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let related_name = rg.get_object_name(related_obj); |
|
|
|
|
|
|
|
|
|
|
|
instances.push(RelationInstanceView { |
|
|
|
instances.push(RelationInstanceView { |
|
|
|
related: related_obj, |
|
|
|
related: ObjectDisplay { |
|
|
|
|
|
|
|
id: related_obj.id, |
|
|
|
|
|
|
|
model: related_obj.model, |
|
|
|
|
|
|
|
name: rg.get_object_name(related_obj), |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
related_name, |
|
|
|
properties: view_rel_properties, |
|
|
|
properties: view_rel_properties, |
|
|
|
}) |
|
|
|
}) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
instances.sort_by_key(|r| &r.related.name); |
|
|
|
instances.sort_by(|a, b| a.related_name.cmp(&b.related_name)); |
|
|
|
|
|
|
|
|
|
|
|
relation_views.push(RelationView { |
|
|
|
relation_views.push(RelationView { |
|
|
|
model: rg.get_relation_model(model_id).unwrap(), |
|
|
|
model: rg.get_relation_model(model_id).unwrap(), |
|
|
@ -322,13 +355,21 @@ pub(crate) async fn detail( |
|
|
|
|
|
|
|
|
|
|
|
view_rel_properties.sort_by_key(|p| &p.model.name); |
|
|
|
view_rel_properties.sort_by_key(|p| &p.model.name); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let related_name = rg.get_object_name(related_obj); |
|
|
|
|
|
|
|
|
|
|
|
instances.push(RelationInstanceView { |
|
|
|
instances.push(RelationInstanceView { |
|
|
|
related: related_obj, |
|
|
|
related: ObjectDisplay { |
|
|
|
|
|
|
|
id: related_obj.id, |
|
|
|
|
|
|
|
model: related_obj.model, |
|
|
|
|
|
|
|
name: rg.get_object_name(related_obj), |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
related_name, |
|
|
|
properties: view_rel_properties, |
|
|
|
properties: view_rel_properties, |
|
|
|
}) |
|
|
|
}) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
instances.sort_by_key(|r| &r.related.name); |
|
|
|
// sort_by_key is not possible because rust is still stupid about lifetimes
|
|
|
|
|
|
|
|
instances.sort_by(|a, b| a.related_name.cmp(&b.related_name)); |
|
|
|
|
|
|
|
|
|
|
|
relation_views.push(RelationView { |
|
|
|
relation_views.push(RelationView { |
|
|
|
model: rg.get_relation_model(model_id).unwrap(), |
|
|
|
model: rg.get_relation_model(model_id).unwrap(), |
|
|
@ -349,7 +390,7 @@ pub(crate) async fn detail( |
|
|
|
struct EnrichedObject<'a> { |
|
|
|
struct EnrichedObject<'a> { |
|
|
|
id: ID, |
|
|
|
id: ID, |
|
|
|
model: ID, |
|
|
|
model: ID, |
|
|
|
name: String, |
|
|
|
name: Cow<'a, str>, |
|
|
|
values: HashMap< |
|
|
|
values: HashMap< |
|
|
|
String, /* ID but as string so serde will stop exploding */ |
|
|
|
String, /* ID but as string so serde will stop exploding */ |
|
|
|
Vec<&'a data::Value>, |
|
|
|
Vec<&'a data::Value>, |
|
|
@ -387,7 +428,11 @@ pub(crate) async fn update_form( |
|
|
|
|
|
|
|
|
|
|
|
// maybe its useful,idk
|
|
|
|
// maybe its useful,idk
|
|
|
|
context.insert("model", &model); |
|
|
|
context.insert("model", &model); |
|
|
|
context.insert("object", &object); |
|
|
|
context.insert("object", &ObjectDisplay { |
|
|
|
|
|
|
|
id: object.id, |
|
|
|
|
|
|
|
model: object.model, |
|
|
|
|
|
|
|
name: rg.get_object_name(object), |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
let create_data = prepare_object_create_data(&rg, model.id)?; |
|
|
|
let create_data = prepare_object_create_data(&rg, model.id)?; |
|
|
|
|
|
|
|
|
|
|
@ -480,7 +525,7 @@ pub(crate) async fn update_form( |
|
|
|
let object = EnrichedObject { |
|
|
|
let object = EnrichedObject { |
|
|
|
id: object.id, |
|
|
|
id: object.id, |
|
|
|
model: object.model, |
|
|
|
model: object.model, |
|
|
|
name: object.name.clone(), |
|
|
|
name: rg.get_object_name_by_id(object.id), |
|
|
|
values: value_map, |
|
|
|
values: value_map, |
|
|
|
relations: relation_map, |
|
|
|
relations: relation_map, |
|
|
|
}; |
|
|
|
}; |
|
|
@ -494,26 +539,26 @@ pub(crate) async fn update_form( |
|
|
|
|
|
|
|
|
|
|
|
#[post("/object/update/{id}")] |
|
|
|
#[post("/object/update/{id}")] |
|
|
|
pub(crate) async fn update( |
|
|
|
pub(crate) async fn update( |
|
|
|
_id: web::Path<ID>, |
|
|
|
id: web::Path<ID>, |
|
|
|
form: web::Json<UpdateObj>, |
|
|
|
form: web::Json<UpdateObj>, |
|
|
|
store: crate::YopaStoreWrapper, |
|
|
|
store: crate::YopaStoreWrapper, |
|
|
|
session: Session, |
|
|
|
session: Session, |
|
|
|
) -> actix_web::Result<HttpResponse> { |
|
|
|
) -> actix_web::Result<HttpResponse> { |
|
|
|
let mut wg = store.write().await; |
|
|
|
let mut wg = store.write().await; |
|
|
|
let form = form.into_inner(); |
|
|
|
let form = form.into_inner(); |
|
|
|
let name = form.name.clone(); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let object = wg |
|
|
|
let object = wg |
|
|
|
.get_object(form.id) |
|
|
|
.get_object(*id) |
|
|
|
.ok_or_else(|| actix_web::error::ErrorNotFound("No such object"))?; |
|
|
|
.ok_or_else(|| actix_web::error::ErrorNotFound("No such object"))?; |
|
|
|
|
|
|
|
|
|
|
|
let model_name = wg.get_model_name(object.model).to_owned().to_title_case(); |
|
|
|
let model_name = wg.get_model_name(object.model).to_owned().to_title_case(); |
|
|
|
|
|
|
|
let id = object.id; |
|
|
|
|
|
|
|
|
|
|
|
match wg.update_object(form) { |
|
|
|
match wg.update_object(form) { |
|
|
|
Ok(_id) => { |
|
|
|
Ok(_id) => { |
|
|
|
wg.persist().err_to_500()?; |
|
|
|
wg.persist().err_to_500()?; |
|
|
|
debug!("Object created, redirecting to root"); |
|
|
|
debug!("Object created, redirecting to root"); |
|
|
|
session.flash_success(format!("{} \"{}\" updated.", model_name, name)); |
|
|
|
session.flash_success(format!("{} \"{}\" updated.", model_name, wg.get_object_name_by_id(id))); |
|
|
|
Ok(HttpResponse::Ok().finish()) |
|
|
|
Ok(HttpResponse::Ok().finish()) |
|
|
|
} |
|
|
|
} |
|
|
|
Err(e) => { |
|
|
|
Err(e) => { |
|
|
@ -530,11 +575,14 @@ pub(crate) async fn delete( |
|
|
|
session: Session, |
|
|
|
session: Session, |
|
|
|
) -> actix_web::Result<impl Responder> { |
|
|
|
) -> actix_web::Result<impl Responder> { |
|
|
|
let mut wg = store.write().await; |
|
|
|
let mut wg = store.write().await; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let name = wg.get_object_name_by_id(*id).to_string(); |
|
|
|
|
|
|
|
|
|
|
|
match wg.delete_object(*id) { |
|
|
|
match wg.delete_object(*id) { |
|
|
|
Ok(obj) => { |
|
|
|
Ok(_obj) => { |
|
|
|
wg.persist().err_to_500()?; |
|
|
|
wg.persist().err_to_500()?; |
|
|
|
debug!("Object deleted, redirecting to root"); |
|
|
|
debug!("Object deleted, redirecting to root"); |
|
|
|
session.flash_success(format!("Object \"{}\" deleted.", obj.name)); |
|
|
|
session.flash_success(format!("Object \"{}\" deleted.", name)); |
|
|
|
redirect("/") |
|
|
|
redirect("/") |
|
|
|
} |
|
|
|
} |
|
|
|
Err(e) => { |
|
|
|
Err(e) => { |
|
|
|