fix warning and reformat all
This commit is contained in:
+2
-1
@@ -10,5 +10,6 @@ fn main() {
|
||||
resource_dir("./resources/static")
|
||||
.with_generated_filename(out_path.join("static_files.rs"))
|
||||
.with_generated_fn("included_static_files")
|
||||
.build().unwrap();
|
||||
.build()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
+156
-146
@@ -3,35 +3,27 @@ extern crate actix_web;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
use std::borrow::Borrow;
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Deref;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use actix_session::CookieSession;
|
||||
use actix_web::{App, get, HttpRequest, HttpResponse, HttpServer, post, Responder, web};
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::web::{scope, service};
|
||||
use actix_web::{web, App, HttpResponse, HttpServer};
|
||||
use actix_web_static_files;
|
||||
use actix_web_static_files::ResourceFiles as StaticFiles;
|
||||
use include_dir::Dir;
|
||||
use log::LevelFilter;
|
||||
use once_cell::sync::Lazy;
|
||||
use parking_lot::Mutex;
|
||||
use rand::Rng;
|
||||
use tera::Tera;
|
||||
|
||||
use yopa::insert::{InsertObj, InsertRel, InsertValue};
|
||||
use yopa::{Storage, TypedValue};
|
||||
|
||||
use crate::tera_ext::TeraExt;
|
||||
use yopa::insert::{InsertObj, InsertValue, InsertRel};
|
||||
|
||||
mod utils;
|
||||
mod tera_ext;
|
||||
mod routes;
|
||||
mod session_ext;
|
||||
mod tera_ext;
|
||||
mod utils;
|
||||
|
||||
// Embed static files
|
||||
include!(concat!(env!("OUT_DIR"), "/static_files.rs"));
|
||||
@@ -45,47 +37,52 @@ pub(crate) static TERA: Lazy<Tera> = Lazy::new(|| {
|
||||
|
||||
// Special filter for the TypedValue map
|
||||
use serde_json::Value;
|
||||
tera.register_filter("print_typed_value", |v: &Value, _: &HashMap<String, Value>| -> tera::Result<Value> {
|
||||
if v.is_null() {
|
||||
return Ok(v.clone());
|
||||
}
|
||||
if let Value::Object(map) = v {
|
||||
if let Some((_, v)) = map.iter().next() {
|
||||
tera.register_filter(
|
||||
"print_typed_value",
|
||||
|v: &Value, _: &HashMap<String, Value>| -> tera::Result<Value> {
|
||||
if v.is_null() {
|
||||
return Ok(v.clone());
|
||||
}
|
||||
}
|
||||
Err(tera::Error::msg("Expected nonenmpty object"))
|
||||
});
|
||||
if let Value::Object(map) = v {
|
||||
if let Some((_, v)) = map.iter().next() {
|
||||
return Ok(v.clone());
|
||||
}
|
||||
}
|
||||
Err(tera::Error::msg("Expected nonenmpty object"))
|
||||
},
|
||||
);
|
||||
|
||||
// opt(checked=foo.is_checked)
|
||||
tera.register_function("opt", |args: &HashMap<String, Value>| -> tera::Result<Value> {
|
||||
if args.len() != 1 {
|
||||
return Err("Expected 1 argument".into());
|
||||
}
|
||||
match args.iter().nth(0) {
|
||||
Some((name, &Value::Bool(true))) => {
|
||||
Ok(Value::String(name.clone()))
|
||||
},
|
||||
Some((_, &Value::Bool(false))) => {
|
||||
Ok(Value::Null)
|
||||
},
|
||||
_ => Err("Expected bool argument".into()),
|
||||
}
|
||||
});
|
||||
tera.register_function(
|
||||
"opt",
|
||||
|args: &HashMap<String, Value>| -> tera::Result<Value> {
|
||||
if args.len() != 1 {
|
||||
return Err("Expected 1 argument".into());
|
||||
}
|
||||
match args.iter().nth(0) {
|
||||
Some((name, &Value::Bool(true))) => Ok(Value::String(name.clone())),
|
||||
Some((_, &Value::Bool(false))) => Ok(Value::Null),
|
||||
_ => Err("Expected bool argument".into()),
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// selected(val=foo.color,opt="Red")
|
||||
tera.register_function("selected", |args: &HashMap<String, Value>| -> tera::Result<Value> {
|
||||
match (args.get("val"), args.get("opt")) {
|
||||
(Some(v), Some(w)) => {
|
||||
if v == w {
|
||||
Ok(Value::String("selected".into()))
|
||||
} else {
|
||||
Ok(Value::Null)
|
||||
tera.register_function(
|
||||
"selected",
|
||||
|args: &HashMap<String, Value>| -> tera::Result<Value> {
|
||||
match (args.get("val"), args.get("opt")) {
|
||||
(Some(v), Some(w)) => {
|
||||
if v == w {
|
||||
Ok(Value::String("selected".into()))
|
||||
} else {
|
||||
Ok(Value::Null)
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => Err("Expected val and opt args".into()),
|
||||
}
|
||||
});
|
||||
_ => Err("Expected val and opt args".into()),
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// TODO need to inject HttpRequest::url_for() into tera context, but it then can't be accessed by the functions.
|
||||
// tera.register_function("url_for", |args: HashMap<String, Value>| -> tera::Result<Value> {
|
||||
@@ -117,19 +114,14 @@ async fn main() -> std::io::Result<()> {
|
||||
debug!("Session key: {:?}", session_key);
|
||||
|
||||
HttpServer::new(move || {
|
||||
let static_files = StaticFiles::new("/static", included_static_files())
|
||||
.do_not_resolve_defaults();
|
||||
let static_files =
|
||||
StaticFiles::new("/static", included_static_files()).do_not_resolve_defaults();
|
||||
|
||||
App::new()
|
||||
/* Middlewares */
|
||||
.wrap(
|
||||
CookieSession::signed(&session_key)
|
||||
.secure(false)
|
||||
)
|
||||
|
||||
.wrap(CookieSession::signed(&session_key).secure(false))
|
||||
/* Bind shared objects */
|
||||
.app_data(yopa_store.clone())
|
||||
|
||||
/* Routes */
|
||||
.service(routes::index)
|
||||
.service(routes::takeout)
|
||||
@@ -163,13 +155,15 @@ async fn main() -> std::io::Result<()> {
|
||||
.service(routes::objects::delete)
|
||||
//
|
||||
.service(static_files)
|
||||
.default_service(web::to(|| HttpResponse::NotFound().body("File or endpoint not found")))
|
||||
.default_service(web::to(|| {
|
||||
HttpResponse::NotFound().body("File or endpoint not found")
|
||||
}))
|
||||
})
|
||||
.bind("127.0.0.1:8080")?
|
||||
.run().await
|
||||
.bind("127.0.0.1:8080")?
|
||||
.run()
|
||||
.await
|
||||
}
|
||||
|
||||
|
||||
fn init_yopa() -> YopaStoreWrapper {
|
||||
let mut store = Storage::new();
|
||||
|
||||
@@ -177,107 +171,123 @@ fn init_yopa() -> YopaStoreWrapper {
|
||||
use yopa::model;
|
||||
use yopa::DataType;
|
||||
|
||||
let id_recipe = store.define_object(model::ObjectModel {
|
||||
id: Default::default(),
|
||||
name: "Recipe".to_string(),
|
||||
}).unwrap();
|
||||
let id_recipe = store
|
||||
.define_object(model::ObjectModel {
|
||||
id: Default::default(),
|
||||
name: "Recipe".to_string(),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let id_book = store.define_object(model::ObjectModel {
|
||||
id: Default::default(),
|
||||
name: "Book".to_string(),
|
||||
}).unwrap();
|
||||
let id_book = store
|
||||
.define_object(model::ObjectModel {
|
||||
id: Default::default(),
|
||||
name: "Book".to_string(),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let id_ing = store.define_object(model::ObjectModel {
|
||||
id: Default::default(),
|
||||
name: "Ingredient".to_string(),
|
||||
}).unwrap();
|
||||
let _id_ing = store
|
||||
.define_object(model::ObjectModel {
|
||||
id: Default::default(),
|
||||
name: "Ingredient".to_string(),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let val_descr = store.define_property(model::PropertyModel {
|
||||
id: Default::default(),
|
||||
object: id_recipe,
|
||||
name: "description".to_string(),
|
||||
optional: true,
|
||||
multiple: true,
|
||||
data_type: DataType::String,
|
||||
default: TypedValue::String("".into()),
|
||||
}).unwrap();
|
||||
let val_descr = store
|
||||
.define_property(model::PropertyModel {
|
||||
id: Default::default(),
|
||||
object: id_recipe,
|
||||
name: "description".to_string(),
|
||||
optional: true,
|
||||
multiple: true,
|
||||
data_type: DataType::String,
|
||||
default: TypedValue::String("".into()),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
store.define_property(model::PropertyModel {
|
||||
id: Default::default(),
|
||||
object: id_book,
|
||||
name: "author".to_string(),
|
||||
optional: true,
|
||||
multiple: true,
|
||||
data_type: DataType::String,
|
||||
default: TypedValue::String("Pepa Novák".into()),
|
||||
}).unwrap();
|
||||
store
|
||||
.define_property(model::PropertyModel {
|
||||
id: Default::default(),
|
||||
object: id_book,
|
||||
name: "author".to_string(),
|
||||
optional: true,
|
||||
multiple: true,
|
||||
data_type: DataType::String,
|
||||
default: TypedValue::String("Pepa Novák".into()),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let rel_book_id = store.define_relation(model::RelationModel {
|
||||
id: Default::default(),
|
||||
object: id_recipe,
|
||||
name: "book reference".to_string(),
|
||||
reciprocal_name: "recipes".to_string(),
|
||||
optional: true,
|
||||
multiple: true,
|
||||
related: id_book,
|
||||
}).unwrap();
|
||||
let rel_book_id = store
|
||||
.define_relation(model::RelationModel {
|
||||
id: Default::default(),
|
||||
object: id_recipe,
|
||||
name: "book reference".to_string(),
|
||||
reciprocal_name: "recipes".to_string(),
|
||||
optional: true,
|
||||
multiple: true,
|
||||
related: id_book,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let page = store.define_property(model::PropertyModel {
|
||||
id: Default::default(),
|
||||
object: rel_book_id,
|
||||
name: "page".to_string(),
|
||||
optional: true,
|
||||
multiple: false,
|
||||
data_type: DataType::Integer,
|
||||
default: TypedValue::Integer(0),
|
||||
}).unwrap();
|
||||
let page = store
|
||||
.define_property(model::PropertyModel {
|
||||
id: Default::default(),
|
||||
object: rel_book_id,
|
||||
name: "page".to_string(),
|
||||
optional: true,
|
||||
multiple: false,
|
||||
data_type: DataType::Integer,
|
||||
default: TypedValue::Integer(0),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
store.define_relation(model::RelationModel {
|
||||
id: Default::default(),
|
||||
object: id_recipe,
|
||||
name: "related recipe".to_string(),
|
||||
reciprocal_name: "related recipe".to_string(),
|
||||
optional: true,
|
||||
multiple: true,
|
||||
related: id_recipe,
|
||||
}).unwrap();
|
||||
store
|
||||
.define_relation(model::RelationModel {
|
||||
id: Default::default(),
|
||||
object: id_recipe,
|
||||
name: "related recipe".to_string(),
|
||||
reciprocal_name: "related recipe".to_string(),
|
||||
optional: true,
|
||||
multiple: true,
|
||||
related: id_recipe,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let book1 = store.insert_object(InsertObj {
|
||||
model: id_book,
|
||||
name: "Book 1".to_string(),
|
||||
values: vec![],
|
||||
relations: vec![]
|
||||
}).unwrap();
|
||||
let book1 = store
|
||||
.insert_object(InsertObj {
|
||||
model: id_book,
|
||||
name: "Book 1".to_string(),
|
||||
values: vec![],
|
||||
relations: vec![],
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
store.insert_object(InsertObj {
|
||||
model: id_book,
|
||||
name: "Book 2".to_string(),
|
||||
values: vec![],
|
||||
relations: vec![]
|
||||
}).unwrap();
|
||||
store
|
||||
.insert_object(InsertObj {
|
||||
model: id_book,
|
||||
name: "Book 2".to_string(),
|
||||
values: vec![],
|
||||
relations: vec![],
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
store.insert_object(InsertObj {
|
||||
model: id_recipe,
|
||||
name: "Recipe1".to_string(),
|
||||
values: vec![
|
||||
InsertValue {
|
||||
store
|
||||
.insert_object(InsertObj {
|
||||
model: id_recipe,
|
||||
name: "Recipe1".to_string(),
|
||||
values: vec![InsertValue {
|
||||
model: val_descr,
|
||||
value: TypedValue::String("Bla bla bla".into())
|
||||
}
|
||||
],
|
||||
relations: vec![
|
||||
InsertRel {
|
||||
value: TypedValue::String("Bla bla bla".into()),
|
||||
}],
|
||||
relations: vec![InsertRel {
|
||||
model: rel_book_id,
|
||||
related: book1,
|
||||
values: vec![
|
||||
InsertValue {
|
||||
model: page,
|
||||
value: TypedValue::Integer(15)
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}).unwrap();
|
||||
values: vec![InsertValue {
|
||||
model: page,
|
||||
value: TypedValue::Integer(15),
|
||||
}],
|
||||
}],
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
web::Data::new(tokio::sync::RwLock::new(store))
|
||||
}
|
||||
|
||||
+7
-18
@@ -1,28 +1,17 @@
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::ops::{DerefMut, Deref};
|
||||
use std::str::FromStr;
|
||||
use std::ops::Deref;
|
||||
|
||||
use actix_session::Session;
|
||||
use actix_web::{HttpRequest, HttpResponse, Responder, web};
|
||||
use actix_web::http::header::IntoHeaderValue;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use yopa::{DataType, ID, Storage, StorageError, TypedValue};
|
||||
use yopa::model::{ObjectModel, PropertyModel, RelationModel};
|
||||
|
||||
use crate::session_ext::SessionExt;
|
||||
use crate::TERA;
|
||||
use crate::tera_ext::TeraExt;
|
||||
use crate::routes::models::relation::RelationModelDisplay;
|
||||
use crate::routes::models::object::ObjectModelDisplay;
|
||||
use actix_web::{HttpResponse, Responder};
|
||||
|
||||
pub(crate) mod models;
|
||||
pub(crate) mod objects;
|
||||
|
||||
#[get("/")]
|
||||
pub(crate) async fn index(session: Session, store: crate::YopaStoreWrapper) -> actix_web::Result<HttpResponse> {
|
||||
objects::list_inner(session, store)
|
||||
.await
|
||||
pub(crate) async fn index(
|
||||
session: Session,
|
||||
store: crate::YopaStoreWrapper,
|
||||
) -> actix_web::Result<HttpResponse> {
|
||||
objects::list_inner(session, store).await
|
||||
}
|
||||
|
||||
#[get("/takeout")]
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
use crate::routes::models::object::ObjectModelDisplay;
|
||||
use crate::routes::models::relation::RelationModelDisplay;
|
||||
use crate::session_ext::SessionExt;
|
||||
use crate::tera_ext::TeraExt;
|
||||
use crate::TERA;
|
||||
use actix_session::Session;
|
||||
use actix_web::Responder;
|
||||
use crate::routes::models::relation::RelationModelDisplay;
|
||||
use crate::routes::models::object::ObjectModelDisplay;
|
||||
use crate::session_ext::SessionExt;
|
||||
use crate::TERA;
|
||||
use crate::tera_ext::TeraExt;
|
||||
|
||||
pub(crate) mod object;
|
||||
pub(crate) mod relation;
|
||||
pub(crate) mod property;
|
||||
pub(crate) mod relation;
|
||||
|
||||
#[get("/models")]
|
||||
pub(crate) async fn list(session: Session, store: crate::YopaStoreWrapper) -> actix_web::Result<impl Responder> {
|
||||
pub(crate) async fn list(
|
||||
session: Session,
|
||||
store: crate::YopaStoreWrapper,
|
||||
) -> actix_web::Result<impl Responder> {
|
||||
let rg = store.read().await;
|
||||
|
||||
let models_iter = rg.get_object_models();
|
||||
@@ -24,31 +27,37 @@ pub(crate) async fn list(session: Session, store: crate::YopaStoreWrapper) -> ac
|
||||
|
||||
let mut models = vec![];
|
||||
for om in models_iter {
|
||||
let mut relations = model_relations.remove(&om.id).unwrap_or_default();
|
||||
let mut relations = relations.into_iter().map(|rm| {
|
||||
let mut rprops = model_props.get(&rm.id).cloned().unwrap_or_default();
|
||||
rprops.sort_by_key(|m| &m.name);
|
||||
let relations = model_relations.remove(&om.id).unwrap_or_default();
|
||||
let mut relations = relations
|
||||
.into_iter()
|
||||
.map(|rm| {
|
||||
let mut rprops = model_props.get(&rm.id).cloned().unwrap_or_default();
|
||||
rprops.sort_by_key(|m| &m.name);
|
||||
|
||||
RelationModelDisplay {
|
||||
model: rm,
|
||||
related_name: rg.get_model_name(rm.related),
|
||||
properties: rprops,
|
||||
}
|
||||
}).collect::<Vec<_>>();
|
||||
RelationModelDisplay {
|
||||
model: rm,
|
||||
related_name: rg.get_model_name(rm.related),
|
||||
properties: rprops,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
relations.sort_by_key(|d| &d.model.name);
|
||||
|
||||
// Relations coming INTO this model
|
||||
let mut reciprocal_relations = model_rec_relations.remove(&om.id).unwrap_or_default();
|
||||
let mut reciprocal_relations = reciprocal_relations.into_iter().map(|rm| {
|
||||
let mut rprops = model_props.get(&rm.id).cloned().unwrap_or_default();
|
||||
rprops.sort_by_key(|m| &m.name);
|
||||
let reciprocal_relations = model_rec_relations.remove(&om.id).unwrap_or_default();
|
||||
let mut reciprocal_relations = reciprocal_relations
|
||||
.into_iter()
|
||||
.map(|rm| {
|
||||
let mut rprops = model_props.get(&rm.id).cloned().unwrap_or_default();
|
||||
rprops.sort_by_key(|m| &m.name);
|
||||
|
||||
RelationModelDisplay {
|
||||
model: rm,
|
||||
related_name: rg.get_model_name(rm.object),
|
||||
properties: rprops,
|
||||
}
|
||||
}).collect::<Vec<_>>();
|
||||
RelationModelDisplay {
|
||||
model: rm,
|
||||
related_name: rg.get_model_name(rm.object),
|
||||
properties: rprops,
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
reciprocal_relations.sort_by_key(|d| &d.model.reciprocal_name);
|
||||
|
||||
let mut properties = model_props.remove(&om.id).unwrap_or_default();
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
use actix_session::Session;
|
||||
use actix_web::{Responder, web};
|
||||
use actix_web::{web, Responder};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use yopa::ID;
|
||||
use yopa::model::{ObjectModel, PropertyModel};
|
||||
use yopa::ID;
|
||||
|
||||
use crate::routes::models::relation::RelationModelDisplay;
|
||||
use crate::session_ext::SessionExt;
|
||||
use crate::TERA;
|
||||
use crate::tera_ext::TeraExt;
|
||||
use crate::utils::redirect;
|
||||
use crate::TERA;
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
pub(crate) struct ObjectModelDisplay<'a> {
|
||||
@@ -60,7 +60,7 @@ pub(crate) async fn create(
|
||||
Err(e) => {
|
||||
warn!("Error creating model: {}", e);
|
||||
session.flash_error(e.to_string());
|
||||
session.set("old", form);
|
||||
session.set("old", form).unwrap();
|
||||
redirect("/model/object/create")
|
||||
}
|
||||
}
|
||||
@@ -76,7 +76,8 @@ pub(crate) async fn update_form(
|
||||
session.render_flash(&mut context);
|
||||
let rg = store.read().await;
|
||||
|
||||
let model = rg.get_object_model(*model_id)
|
||||
let model = rg
|
||||
.get_object_model(*model_id)
|
||||
.ok_or_else(|| actix_web::error::ErrorNotFound("No such model"))?;
|
||||
|
||||
// Re-fill old values
|
||||
@@ -114,13 +115,12 @@ pub(crate) async fn update(
|
||||
Err(e) => {
|
||||
warn!("Error updating model: {}", e);
|
||||
session.flash_error(e.to_string());
|
||||
session.set("old", form);
|
||||
session.set("old", form).unwrap();
|
||||
redirect(format!("/model/object/update/{}", id))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[get("/model/object/delete/{id}")]
|
||||
pub(crate) async fn delete(
|
||||
id: web::Path<String>,
|
||||
@@ -128,7 +128,10 @@ pub(crate) async fn delete(
|
||||
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))?) {
|
||||
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));
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
use actix_session::Session;
|
||||
use actix_web::{Responder, web};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use actix_web::{web, Responder};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use yopa::{DataType, ID, TypedValue};
|
||||
use yopa::model::{PropertyModel, RelationModel};
|
||||
use yopa::model::PropertyModel;
|
||||
use yopa::{DataType, TypedValue, ID};
|
||||
|
||||
use crate::session_ext::SessionExt;
|
||||
use crate::TERA;
|
||||
use crate::tera_ext::TeraExt;
|
||||
use crate::utils::{ParseOrBadReq, redirect};
|
||||
use crate::routes::models::relation::ObjectOrRelationModelDisplay;
|
||||
use crate::session_ext::SessionExt;
|
||||
use crate::tera_ext::TeraExt;
|
||||
use crate::utils::{redirect, ParseOrBadReq};
|
||||
use crate::TERA;
|
||||
|
||||
#[get("/model/property/create/{object_id}")]
|
||||
pub(crate) async fn create_form(
|
||||
@@ -26,14 +26,17 @@ pub(crate) async fn create_form(
|
||||
if let Ok(Some(form)) = session.take::<PropertyModelCreateForm>("old") {
|
||||
context.insert("old", &form);
|
||||
} else {
|
||||
context.insert("old", &PropertyModelCreateForm {
|
||||
object: Default::default(),
|
||||
name: "".to_string(),
|
||||
optional: true,
|
||||
multiple: false,
|
||||
data_type: DataType::String,
|
||||
default: "".to_string()
|
||||
});
|
||||
context.insert(
|
||||
"old",
|
||||
&PropertyModelCreateForm {
|
||||
object: Default::default(),
|
||||
name: "".to_string(),
|
||||
optional: true,
|
||||
multiple: false,
|
||||
data_type: DataType::String,
|
||||
default: "".to_string(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
debug!("ID = {}", object_id);
|
||||
@@ -60,7 +63,7 @@ pub(crate) async fn create_form(
|
||||
TERA.build_response("models/property_create", &context)
|
||||
}
|
||||
|
||||
#[derive(Serialize,Deserialize)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub(crate) struct PropertyModelCreateForm {
|
||||
pub object: ID,
|
||||
pub name: String,
|
||||
@@ -74,20 +77,19 @@ pub(crate) struct PropertyModelCreateForm {
|
||||
pub default: String,
|
||||
}
|
||||
|
||||
fn parse_default(data_type : DataType, default : String) -> Result<TypedValue, String> {
|
||||
fn parse_default(data_type: DataType, default: String) -> Result<TypedValue, String> {
|
||||
Ok(match data_type {
|
||||
DataType::String => {
|
||||
TypedValue::String(default.into())
|
||||
}
|
||||
DataType::String => TypedValue::String(default.into()),
|
||||
DataType::Integer => {
|
||||
if default.is_empty() {
|
||||
TypedValue::Integer(0)
|
||||
} else {
|
||||
// TODO better error reporting
|
||||
TypedValue::Integer(default.parse()
|
||||
.map_err(|_| {
|
||||
format!("Error parsing \"{}\" as integer", default)
|
||||
})?)
|
||||
TypedValue::Integer(
|
||||
default
|
||||
.parse()
|
||||
.map_err(|_| format!("Error parsing \"{}\" as integer", default))?,
|
||||
)
|
||||
}
|
||||
}
|
||||
DataType::Decimal => {
|
||||
@@ -95,10 +97,11 @@ fn parse_default(data_type : DataType, default : String) -> Result<TypedValue, S
|
||||
TypedValue::Decimal(0.0)
|
||||
} else {
|
||||
// TODO better error reporting
|
||||
TypedValue::Decimal(default.parse()
|
||||
.map_err(|_| {
|
||||
format!("Error parsing \"{}\" as decimal", default)
|
||||
})?)
|
||||
TypedValue::Decimal(
|
||||
default
|
||||
.parse()
|
||||
.map_err(|_| format!("Error parsing \"{}\" as decimal", default))?,
|
||||
)
|
||||
}
|
||||
}
|
||||
DataType::Boolean => {
|
||||
@@ -106,9 +109,8 @@ fn parse_default(data_type : DataType, default : String) -> Result<TypedValue, S
|
||||
TypedValue::Boolean(false)
|
||||
} else {
|
||||
TypedValue::String(default.clone().into())
|
||||
.cast_to(DataType::Boolean).map_err(|_| {
|
||||
format!("Error parsing \"{}\" as boolean", default)
|
||||
})?
|
||||
.cast_to(DataType::Boolean)
|
||||
.map_err(|_| format!("Error parsing \"{}\" as boolean", default))?
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -131,7 +133,7 @@ pub(crate) async fn create(
|
||||
Err(msg) => {
|
||||
warn!("{}", msg);
|
||||
session.flash_error(msg);
|
||||
session.set("old", &form);
|
||||
session.set("old", &form).unwrap();
|
||||
return redirect(format!("/model/property/create/{}", form.object));
|
||||
}
|
||||
};
|
||||
@@ -153,7 +155,7 @@ pub(crate) async fn create(
|
||||
Err(e) => {
|
||||
warn!("Error creating property model: {}", e);
|
||||
session.flash_error(e.to_string());
|
||||
session.set("old", &form);
|
||||
session.set("old", &form).unwrap();
|
||||
redirect(format!("/model/property/create/{}", form.object))
|
||||
}
|
||||
}
|
||||
@@ -193,7 +195,6 @@ pub(crate) struct PropertyModelEditForm {
|
||||
pub default: String,
|
||||
}
|
||||
|
||||
|
||||
#[get("/model/property/update/{model_id}")]
|
||||
pub(crate) async fn update_form(
|
||||
model_id: web::Path<ID>,
|
||||
@@ -204,7 +205,8 @@ pub(crate) async fn update_form(
|
||||
session.render_flash(&mut context);
|
||||
let rg = store.read().await;
|
||||
|
||||
let model = rg.get_property_model(*model_id)
|
||||
let model = rg
|
||||
.get_property_model(*model_id)
|
||||
.ok_or_else(|| actix_web::error::ErrorNotFound("No such model"))?;
|
||||
|
||||
// Re-fill old values
|
||||
@@ -239,7 +241,7 @@ pub(crate) async fn update(
|
||||
Err(msg) => {
|
||||
warn!("{}", msg);
|
||||
session.flash_error(msg);
|
||||
session.set("old", form);
|
||||
session.set("old", form).unwrap();
|
||||
return redirect(format!("/model/property/update/{}", id));
|
||||
}
|
||||
};
|
||||
@@ -261,7 +263,7 @@ pub(crate) async fn update(
|
||||
Err(e) => {
|
||||
warn!("Error updating model: {}", e);
|
||||
session.flash_error(e.to_string());
|
||||
session.set("old", form);
|
||||
session.set("old", form).unwrap();
|
||||
redirect(format!("/model/relation/update/{}", id))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
use actix_session::Session;
|
||||
use actix_web::{Responder, web};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use actix_web::{web, Responder};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use yopa::ID;
|
||||
use yopa::model::{PropertyModel, RelationModel};
|
||||
use yopa::ID;
|
||||
|
||||
use crate::session_ext::SessionExt;
|
||||
use crate::TERA;
|
||||
use crate::tera_ext::TeraExt;
|
||||
use crate::utils::{ParseOrBadReq, redirect};
|
||||
use crate::utils::{redirect, ParseOrBadReq};
|
||||
use crate::TERA;
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
pub(crate) struct RelationModelDisplay<'a> {
|
||||
@@ -33,17 +33,21 @@ pub(crate) async fn create_form(
|
||||
if let Ok(Some(form)) = session.take::<RelationModelCreateForm>("old") {
|
||||
context.insert("old", &form);
|
||||
} else {
|
||||
context.insert("old", &RelationModelCreateForm {
|
||||
object: Default::default(),
|
||||
name: "".to_string(),
|
||||
reciprocal_name: "".to_string(),
|
||||
optional: false,
|
||||
multiple: false,
|
||||
related: Default::default()
|
||||
});
|
||||
context.insert(
|
||||
"old",
|
||||
&RelationModelCreateForm {
|
||||
object: Default::default(),
|
||||
name: "".to_string(),
|
||||
reciprocal_name: "".to_string(),
|
||||
optional: false,
|
||||
multiple: false,
|
||||
related: Default::default(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let object = rg.get_object_model(object_id.parse_or_bad_request()?)
|
||||
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();
|
||||
@@ -56,7 +60,7 @@ pub(crate) async fn create_form(
|
||||
TERA.build_response("models/relation_create", &context)
|
||||
}
|
||||
|
||||
#[derive(Serialize,Deserialize)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub(crate) struct RelationModelCreateForm {
|
||||
pub object: ID,
|
||||
pub name: String,
|
||||
@@ -93,7 +97,7 @@ pub(crate) async fn create(
|
||||
Err(e) => {
|
||||
warn!("Error creating relation model: {}", e);
|
||||
session.flash_error(e.to_string());
|
||||
session.set("old", &form);
|
||||
session.set("old", &form).unwrap();
|
||||
redirect(format!("/model/relation/create/{}", form.object))
|
||||
}
|
||||
}
|
||||
@@ -126,7 +130,7 @@ pub(crate) async fn delete(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize,Deserialize)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub(crate) struct RelationModelEditForm {
|
||||
pub name: String,
|
||||
pub reciprocal_name: String,
|
||||
@@ -146,7 +150,8 @@ pub(crate) async fn update_form(
|
||||
session.render_flash(&mut context);
|
||||
let rg = store.read().await;
|
||||
|
||||
let model = rg.get_relation_model(*model_id)
|
||||
let model = rg
|
||||
.get_relation_model(*model_id)
|
||||
.ok_or_else(|| actix_web::error::ErrorNotFound("No such model"))?;
|
||||
|
||||
// Re-fill old values
|
||||
@@ -181,7 +186,7 @@ pub(crate) async fn update(
|
||||
reciprocal_name: form.reciprocal_name.to_string(),
|
||||
optional: form.optional,
|
||||
multiple: form.multiple,
|
||||
related: Default::default() // dummy
|
||||
related: Default::default(), // dummy
|
||||
}) {
|
||||
Ok(_id) => {
|
||||
debug!("Relation updated, redirecting to root");
|
||||
@@ -191,7 +196,7 @@ pub(crate) async fn update(
|
||||
Err(e) => {
|
||||
warn!("Error updating model: {}", e);
|
||||
session.flash_error(e.to_string());
|
||||
session.set("old", form);
|
||||
session.set("old", form).unwrap();
|
||||
redirect(format!("/model/relation/update/{}", id))
|
||||
}
|
||||
}
|
||||
|
||||
+137
-89
@@ -1,21 +1,20 @@
|
||||
use actix_session::Session;
|
||||
use actix_web::{Responder, web, HttpResponse};
|
||||
use crate::session_ext::SessionExt;
|
||||
use crate::routes::models::object::ObjectModelForm;
|
||||
use crate::TERA;
|
||||
use actix_session::Session;
|
||||
use actix_web::{web, HttpResponse, Responder};
|
||||
|
||||
use crate::tera_ext::TeraExt;
|
||||
use yopa::{ID, model, Storage, data, TypedValue};
|
||||
use yopa::data::Object;
|
||||
use serde::{Serialize,Deserialize};
|
||||
use yopa::insert::{InsertObj, InsertValue};
|
||||
use crate::utils::redirect;
|
||||
use actix_web::web::Json;
|
||||
use serde_json::Value;
|
||||
use itertools::Itertools;
|
||||
use yopa::model::{ObjectModel, PropertyModel, RelationModel};
|
||||
use crate::TERA;
|
||||
use serde::Serialize;
|
||||
use yopa::data::Object;
|
||||
use yopa::insert::InsertObj;
|
||||
use yopa::{data, model, Storage, ID};
|
||||
|
||||
use heck::TitleCase;
|
||||
use std::collections::HashMap;
|
||||
use itertools::Itertools;
|
||||
use json_dotpath::DotPaths;
|
||||
use std::collections::HashMap;
|
||||
use yopa::model::{ObjectModel, PropertyModel, RelationModel};
|
||||
use yopa::update::UpdateObj;
|
||||
|
||||
// we only need references here, Context serializes everything to Value.
|
||||
@@ -28,7 +27,7 @@ pub struct Schema<'a> {
|
||||
pub prop_models: Vec<&'a model::PropertyModel>,
|
||||
}
|
||||
|
||||
#[derive(Serialize,Debug,Clone)]
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
pub struct ObjectCreateData<'a> {
|
||||
pub model_id: ID,
|
||||
pub schema: Schema<'a>,
|
||||
@@ -39,14 +38,15 @@ pub struct ObjectCreateData<'a> {
|
||||
pub(crate) async fn create_form(
|
||||
model_id: web::Path<ID>,
|
||||
store: crate::YopaStoreWrapper,
|
||||
session: Session
|
||||
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)
|
||||
let model = rg
|
||||
.get_object_model(*model_id)
|
||||
.ok_or_else(|| actix_web::error::ErrorNotFound("No such model"))?;
|
||||
|
||||
context.insert("model", model);
|
||||
@@ -58,19 +58,20 @@ pub(crate) async fn create_form(
|
||||
TERA.build_response("objects/object_create", &context)
|
||||
}
|
||||
|
||||
fn prepare_object_create_data(rg : &Storage, model_id : ID) -> actix_web::Result<ObjectCreateData> {
|
||||
let model = rg.get_object_model(model_id)
|
||||
fn prepare_object_create_data(rg: &Storage, model_id: ID) -> actix_web::Result<ObjectCreateData> {
|
||||
let model = rg
|
||||
.get_object_model(model_id)
|
||||
.ok_or_else(|| actix_web::error::ErrorNotFound("No such model"))?;
|
||||
|
||||
let relations : Vec<_> = rg.get_relation_models_for_object_model(model.id).collect();
|
||||
let relations: Vec<_> = rg.get_relation_models_for_object_model(model.id).collect();
|
||||
|
||||
let mut prop_object_ids : Vec<ID> = relations.iter().map(|r| r.id).collect();
|
||||
let mut prop_object_ids: Vec<ID> = relations.iter().map(|r| r.id).collect();
|
||||
prop_object_ids.push(model.id);
|
||||
|
||||
prop_object_ids.sort();
|
||||
prop_object_ids.dedup();
|
||||
|
||||
let mut related_ids : Vec<_> = relations.iter().map(|r| r.related).collect();
|
||||
let mut related_ids: Vec<_> = relations.iter().map(|r| r.related).collect();
|
||||
|
||||
related_ids.sort();
|
||||
related_ids.dedup();
|
||||
@@ -80,9 +81,11 @@ fn prepare_object_create_data(rg : &Storage, model_id : ID) -> actix_web::Result
|
||||
schema: Schema {
|
||||
obj_models: rg.get_object_models().collect(), // TODO get only the ones that matter here
|
||||
rel_models: relations,
|
||||
prop_models: rg.get_property_models_for_parents(prop_object_ids).collect()
|
||||
prop_models: rg
|
||||
.get_property_models_for_parents(prop_object_ids)
|
||||
.collect(),
|
||||
},
|
||||
objects: rg.get_objects_of_type(related_ids).collect()
|
||||
objects: rg.get_objects_of_type(related_ids).collect(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -120,30 +123,35 @@ pub(crate) async fn create(
|
||||
|
||||
#[derive(Debug, Serialize, Clone)]
|
||||
struct ModelWithObjects<'a> {
|
||||
model : &'a ObjectModel,
|
||||
objects: Vec<&'a Object>
|
||||
model: &'a ObjectModel,
|
||||
objects: Vec<&'a Object>,
|
||||
}
|
||||
|
||||
#[get("/objects")]
|
||||
pub(crate) async fn list(session: Session, store: crate::YopaStoreWrapper) -> actix_web::Result<impl Responder> {
|
||||
pub(crate) async fn list(
|
||||
session: Session,
|
||||
store: crate::YopaStoreWrapper,
|
||||
) -> actix_web::Result<impl Responder> {
|
||||
list_inner(session, store).await
|
||||
}
|
||||
|
||||
pub(crate) async fn list_inner(session: Session, store: crate::YopaStoreWrapper) -> actix_web::Result<HttpResponse> {
|
||||
pub(crate) async fn list_inner(
|
||||
session: Session,
|
||||
store: crate::YopaStoreWrapper,
|
||||
) -> actix_web::Result<HttpResponse> {
|
||||
let rg = store.read().await;
|
||||
|
||||
let mut objects_by_model = rg.get_grouped_objects();
|
||||
|
||||
let mut models : Vec<_> = rg.get_object_models()
|
||||
let models: Vec<_> = rg
|
||||
.get_object_models()
|
||||
.sorted_by_key(|m| &m.name)
|
||||
.map(|model| {
|
||||
let mut objects = objects_by_model.remove(&model.id).unwrap_or_default();
|
||||
objects.sort_by_key(|o| &o.name);
|
||||
ModelWithObjects {
|
||||
model,
|
||||
objects
|
||||
}
|
||||
}).collect();
|
||||
ModelWithObjects { model, objects }
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut ctx = tera::Context::new();
|
||||
ctx.insert("models", &models);
|
||||
@@ -152,31 +160,30 @@ pub(crate) async fn list_inner(session: Session, store: crate::YopaStoreWrapper)
|
||||
TERA.build_response("objects/index", &ctx)
|
||||
}
|
||||
|
||||
#[derive(Debug,Serialize)]
|
||||
#[derive(Debug, Serialize)]
|
||||
struct PropertyView<'a> {
|
||||
model : &'a PropertyModel,
|
||||
values: Vec<&'a yopa::data::Value>
|
||||
model: &'a PropertyModel,
|
||||
values: Vec<&'a yopa::data::Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug,Serialize)]
|
||||
#[derive(Debug, Serialize)]
|
||||
struct RelationView<'a> {
|
||||
model : &'a RelationModel,
|
||||
model: &'a RelationModel,
|
||||
related_name: &'a str,
|
||||
instances: Vec<RelationInstanceView<'a>>
|
||||
instances: Vec<RelationInstanceView<'a>>,
|
||||
}
|
||||
|
||||
#[derive(Debug,Serialize)]
|
||||
#[derive(Debug, Serialize)]
|
||||
struct RelationInstanceView<'a> {
|
||||
related: &'a Object,
|
||||
properties: Vec<PropertyView<'a>>
|
||||
properties: Vec<PropertyView<'a>>,
|
||||
}
|
||||
|
||||
|
||||
#[get("/object/detail/{id}")]
|
||||
pub(crate) async fn detail(
|
||||
id: web::Path<ID>,
|
||||
store: crate::YopaStoreWrapper,
|
||||
session: Session
|
||||
session: Session,
|
||||
) -> actix_web::Result<impl Responder> {
|
||||
let object_id = *id;
|
||||
|
||||
@@ -185,18 +192,22 @@ pub(crate) async fn detail(
|
||||
|
||||
let rg = store.read().await;
|
||||
|
||||
let object = rg.get_object(object_id)
|
||||
let object = rg
|
||||
.get_object(object_id)
|
||||
.ok_or_else(|| actix_web::error::ErrorNotFound("No such object"))?;
|
||||
|
||||
let model = rg.get_object_model(object.model)
|
||||
let model = rg
|
||||
.get_object_model(object.model)
|
||||
.ok_or_else(|| actix_web::error::ErrorNotFound("No such model"))?;
|
||||
|
||||
context.insert("object", object);
|
||||
context.insert("model", model);
|
||||
context.insert("kind", &rg.get_model_name(object.model));
|
||||
|
||||
let mut relations = rg.get_relations_for_object(object_id).collect_vec();
|
||||
let reci_relations = rg.get_reciprocal_relations_for_object(object_id).collect_vec();
|
||||
let relations = rg.get_relations_for_object(object_id).collect_vec();
|
||||
let reci_relations = rg
|
||||
.get_reciprocal_relations_for_object(object_id)
|
||||
.collect_vec();
|
||||
|
||||
// values by parent ID
|
||||
let mut ids_to_get_values_for = relations.iter().map(|r| r.id).collect_vec();
|
||||
@@ -207,15 +218,17 @@ pub(crate) async fn detail(
|
||||
|
||||
// object's own properties
|
||||
{
|
||||
let mut object_values_by_model = grouped_values
|
||||
.remove(&object_id).unwrap_or_default().into_iter()
|
||||
let object_values_by_model = grouped_values
|
||||
.remove(&object_id)
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.into_group_map_by(|value| value.model);
|
||||
|
||||
let mut view_object_properties = vec![];
|
||||
for (prop_model_id, values) in object_values_by_model {
|
||||
view_object_properties.push(PropertyView {
|
||||
model: rg.get_property_model(prop_model_id).unwrap(),
|
||||
values
|
||||
values,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -226,7 +239,8 @@ pub(crate) async fn detail(
|
||||
|
||||
// go through relations
|
||||
{
|
||||
let grouped_relations = relations.iter()
|
||||
let grouped_relations = relations
|
||||
.iter()
|
||||
.into_group_map_by(|relation| relation.model);
|
||||
|
||||
let mut relation_views = vec![];
|
||||
@@ -236,18 +250,20 @@ pub(crate) async fn detail(
|
||||
for rel in relations {
|
||||
let related_obj = match rg.get_object(rel.related) {
|
||||
None => continue,
|
||||
Some(obj) => obj
|
||||
Some(obj) => obj,
|
||||
};
|
||||
|
||||
let mut rel_values_by_model = grouped_values
|
||||
.remove(&rel.id).unwrap_or_default().into_iter()
|
||||
let rel_values_by_model = grouped_values
|
||||
.remove(&rel.id)
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.into_group_map_by(|value| value.model);
|
||||
|
||||
let mut view_rel_properties = vec![];
|
||||
for (prop_model_id, values) in rel_values_by_model {
|
||||
view_rel_properties.push(PropertyView {
|
||||
model: rg.get_property_model(prop_model_id).unwrap(),
|
||||
values
|
||||
values,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -255,7 +271,7 @@ pub(crate) async fn detail(
|
||||
|
||||
instances.push(RelationInstanceView {
|
||||
related: related_obj,
|
||||
properties: view_rel_properties
|
||||
properties: view_rel_properties,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -264,7 +280,7 @@ pub(crate) async fn detail(
|
||||
relation_views.push(RelationView {
|
||||
model: rg.get_relation_model(model_id).unwrap(),
|
||||
related_name: rg.get_model_name(model_id),
|
||||
instances
|
||||
instances,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -275,7 +291,8 @@ pub(crate) async fn detail(
|
||||
|
||||
// near-copypasta for reciprocal
|
||||
{
|
||||
let grouped_relations = reci_relations.iter()
|
||||
let grouped_relations = reci_relations
|
||||
.iter()
|
||||
.into_group_map_by(|relation| relation.model);
|
||||
|
||||
let mut relation_views = vec![];
|
||||
@@ -285,18 +302,20 @@ pub(crate) async fn detail(
|
||||
for rel in relations {
|
||||
let related_obj = match rg.get_object(rel.object) {
|
||||
None => continue,
|
||||
Some(obj) => obj
|
||||
Some(obj) => obj,
|
||||
};
|
||||
|
||||
let mut rel_values_by_model = grouped_values
|
||||
.remove(&rel.id).unwrap_or_default().into_iter()
|
||||
let rel_values_by_model = grouped_values
|
||||
.remove(&rel.id)
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.into_group_map_by(|value| value.model);
|
||||
|
||||
let mut view_rel_properties = vec![];
|
||||
for (prop_model_id, values) in rel_values_by_model {
|
||||
view_rel_properties.push(PropertyView {
|
||||
model: rg.get_property_model(prop_model_id).unwrap(),
|
||||
values
|
||||
values,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -304,7 +323,7 @@ pub(crate) async fn detail(
|
||||
|
||||
instances.push(RelationInstanceView {
|
||||
related: related_obj,
|
||||
properties: view_rel_properties
|
||||
properties: view_rel_properties,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -313,7 +332,7 @@ pub(crate) async fn detail(
|
||||
relation_views.push(RelationView {
|
||||
model: rg.get_relation_model(model_id).unwrap(),
|
||||
related_name: rg.get_model_name(model_id),
|
||||
instances
|
||||
instances,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -325,16 +344,19 @@ pub(crate) async fn detail(
|
||||
TERA.build_response("objects/object_detail", &context)
|
||||
}
|
||||
|
||||
#[derive(Serialize,Debug,Clone)]
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
struct EnrichedObject<'a> {
|
||||
id: ID,
|
||||
model: ID,
|
||||
name: String,
|
||||
values: HashMap<String /* ID but as string so serde will stop exploding */, Vec<&'a data::Value>>,
|
||||
values: HashMap<
|
||||
String, /* ID but as string so serde will stop exploding */
|
||||
Vec<&'a data::Value>,
|
||||
>,
|
||||
relations: HashMap<String /* ID */, Vec<EnrichedRelation<'a>>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize,Debug,Clone)]
|
||||
#[derive(Serialize, Debug, Clone)]
|
||||
struct EnrichedRelation<'a> {
|
||||
id: ID,
|
||||
object: ID,
|
||||
@@ -347,17 +369,19 @@ struct EnrichedRelation<'a> {
|
||||
pub(crate) async fn update_form(
|
||||
id: web::Path<ID>,
|
||||
store: crate::YopaStoreWrapper,
|
||||
session: Session
|
||||
session: Session,
|
||||
) -> actix_web::Result<impl Responder> {
|
||||
let mut context = tera::Context::new();
|
||||
session.render_flash(&mut context);
|
||||
|
||||
let rg = store.read().await;
|
||||
|
||||
let object = rg.get_object(*id)
|
||||
let object = rg
|
||||
.get_object(*id)
|
||||
.ok_or_else(|| actix_web::error::ErrorNotFound("No such object"))?;
|
||||
|
||||
let model = rg.get_object_model(object.model)
|
||||
let model = rg
|
||||
.get_object_model(object.model)
|
||||
.ok_or_else(|| actix_web::error::ErrorNotFound("Object has no model"))?;
|
||||
|
||||
// maybe its useful,idk
|
||||
@@ -370,47 +394,71 @@ pub(crate) async fn update_form(
|
||||
let mut relation_map = HashMap::new();
|
||||
|
||||
// Some properties may have no values, so we first check what IDs to expect
|
||||
let prop_ids = create_data.schema.prop_models.iter()
|
||||
.filter(|p| p.object == model.id).map(|p| p.id).collect_vec();
|
||||
let prop_ids = create_data
|
||||
.schema
|
||||
.prop_models
|
||||
.iter()
|
||||
.filter(|p| p.object == model.id)
|
||||
.map(|p| p.id)
|
||||
.collect_vec();
|
||||
|
||||
let mut values_grouped = rg.get_values_for_object(*id)
|
||||
.into_group_map_by(|v| v.model);
|
||||
let mut values_grouped = rg.get_values_for_object(*id).into_group_map_by(|v| v.model);
|
||||
|
||||
prop_ids.into_iter().for_each(|id| {
|
||||
value_map.insert(id.to_string(), values_grouped.remove(&id).unwrap_or_default());
|
||||
value_map.insert(
|
||||
id.to_string(),
|
||||
values_grouped.remove(&id).unwrap_or_default(),
|
||||
);
|
||||
});
|
||||
|
||||
{
|
||||
// Some properties may have no values, so we first check what IDs to expect
|
||||
let relation_model_ids = create_data.schema.rel_models.iter()
|
||||
let relation_model_ids = create_data
|
||||
.schema
|
||||
.rel_models
|
||||
.iter()
|
||||
.filter(|p| p.object == model.id)
|
||||
.map(|p| p.id).collect_vec();
|
||||
.map(|p| p.id)
|
||||
.collect_vec();
|
||||
|
||||
let relations = rg.get_relations_for_object(*id).collect_vec();
|
||||
let relation_ids = relations.iter().map(|r| r.id).collect_vec();
|
||||
|
||||
let mut relations_grouped_by_model = relations.iter()
|
||||
let mut relations_grouped_by_model = relations
|
||||
.iter()
|
||||
.into_group_map_by(|relation| relation.model);
|
||||
|
||||
let mut property_models_grouped_by_parent = rg.get_grouped_prop_models_for_parents(relation_model_ids.clone());
|
||||
let mut property_models_grouped_by_parent =
|
||||
rg.get_grouped_prop_models_for_parents(relation_model_ids.clone());
|
||||
|
||||
let mut relation_values_grouped_by_instance = rg.get_grouped_values_for_objects(relation_ids);
|
||||
let mut relation_values_grouped_by_instance =
|
||||
rg.get_grouped_values_for_objects(relation_ids);
|
||||
|
||||
for rel_model_id in relation_model_ids {
|
||||
let relations = relations_grouped_by_model.remove(&rel_model_id).unwrap_or_default();
|
||||
let relations = relations_grouped_by_model
|
||||
.remove(&rel_model_id)
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut instances = vec![];
|
||||
let prop_models_for_relation = property_models_grouped_by_parent.remove(&rel_model_id).unwrap_or_default();
|
||||
let prop_models_for_relation = property_models_grouped_by_parent
|
||||
.remove(&rel_model_id)
|
||||
.unwrap_or_default();
|
||||
|
||||
for rel in relations {
|
||||
let mut relation_values_map = HashMap::new();
|
||||
|
||||
// values keyed by model
|
||||
let mut rel_values = relation_values_grouped_by_instance.remove(&rel.id).unwrap_or_default().into_iter()
|
||||
let mut rel_values = relation_values_grouped_by_instance
|
||||
.remove(&rel.id)
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.into_group_map_by(|relation| relation.model);
|
||||
|
||||
prop_models_for_relation.iter().for_each(|prop_model| {
|
||||
relation_values_map.insert(prop_model.id.to_string(), rel_values.remove(&prop_model.id).unwrap_or_default());
|
||||
relation_values_map.insert(
|
||||
prop_model.id.to_string(),
|
||||
rel_values.remove(&prop_model.id).unwrap_or_default(),
|
||||
);
|
||||
});
|
||||
|
||||
instances.push(EnrichedRelation {
|
||||
@@ -418,7 +466,7 @@ pub(crate) async fn update_form(
|
||||
object: rel.object,
|
||||
model: rel.model,
|
||||
related: rel.related,
|
||||
values: relation_values_map
|
||||
values: relation_values_map,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -433,7 +481,7 @@ pub(crate) async fn update_form(
|
||||
model: object.model,
|
||||
name: object.name.clone(),
|
||||
values: value_map,
|
||||
relations: relation_map
|
||||
relations: relation_map,
|
||||
};
|
||||
|
||||
form.dot_remove("model_id").unwrap();
|
||||
@@ -443,10 +491,9 @@ pub(crate) async fn update_form(
|
||||
TERA.build_response("objects/object_update", &context)
|
||||
}
|
||||
|
||||
|
||||
#[post("/object/update/{id}")]
|
||||
pub(crate) async fn update(
|
||||
id: web::Path<ID>,
|
||||
_id: web::Path<ID>,
|
||||
form: web::Json<UpdateObj>,
|
||||
store: crate::YopaStoreWrapper,
|
||||
session: Session,
|
||||
@@ -457,7 +504,8 @@ pub(crate) async fn update(
|
||||
let form = form.into_inner();
|
||||
let name = form.name.clone();
|
||||
|
||||
let object = wg.get_object(form.id)
|
||||
let object = wg
|
||||
.get_object(form.id)
|
||||
.ok_or_else(|| actix_web::error::ErrorNotFound("No such object"))?;
|
||||
|
||||
let model_name = wg.get_model_name(object.model).to_owned().to_title_case();
|
||||
|
||||
@@ -3,9 +3,20 @@ use actix_web::HttpResponse;
|
||||
use include_dir::Dir;
|
||||
use tera::Tera;
|
||||
|
||||
fn tera_includedir_walk_folder_inner(collected: &mut Vec<(String, String)>, tera: &mut Tera, dir: &Dir) {
|
||||
fn tera_includedir_walk_folder_inner(
|
||||
collected: &mut Vec<(String, String)>,
|
||||
tera: &mut Tera,
|
||||
dir: &Dir,
|
||||
) {
|
||||
for f in dir.files() {
|
||||
let halves: Vec<_> = f.path().file_name().unwrap().to_str().unwrap().split('.').collect();
|
||||
let halves: Vec<_> = f
|
||||
.path()
|
||||
.file_name()
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.split('.')
|
||||
.collect();
|
||||
|
||||
if halves.last().unwrap() != &"tera" {
|
||||
debug!("Bad file: {:?}", f);
|
||||
@@ -30,11 +41,20 @@ fn tera_includedir_walk_folder_inner(collected: &mut Vec<(String, String)>, tera
|
||||
pub(crate) trait TeraExt {
|
||||
fn add_include_dir_templates(&mut self, dir: &Dir) -> tera::Result<()>;
|
||||
|
||||
fn build_response(&self, template: &str, context: &tera::Context) -> actix_web::Result<HttpResponse> {
|
||||
fn build_response(
|
||||
&self,
|
||||
template: &str,
|
||||
context: &tera::Context,
|
||||
) -> actix_web::Result<HttpResponse> {
|
||||
self.build_err_response(StatusCode::OK, template, context)
|
||||
}
|
||||
|
||||
fn build_err_response(&self, code: StatusCode, template: &str, context: &tera::Context) -> actix_web::Result<HttpResponse>;
|
||||
fn build_err_response(
|
||||
&self,
|
||||
code: StatusCode,
|
||||
template: &str,
|
||||
context: &tera::Context,
|
||||
) -> actix_web::Result<HttpResponse>;
|
||||
}
|
||||
|
||||
impl TeraExt for Tera {
|
||||
@@ -45,10 +65,15 @@ impl TeraExt for Tera {
|
||||
self.add_raw_templates(templates)
|
||||
}
|
||||
|
||||
fn build_err_response(&self, code: StatusCode, template: &str, context: &tera::Context) -> actix_web::Result<HttpResponse> {
|
||||
let html = self.render(template, context).map_err(|e| {
|
||||
actix_web::error::ErrorInternalServerError(e)
|
||||
})?;
|
||||
fn build_err_response(
|
||||
&self,
|
||||
code: StatusCode,
|
||||
template: &str,
|
||||
context: &tera::Context,
|
||||
) -> actix_web::Result<HttpResponse> {
|
||||
let html = self
|
||||
.render(template, context)
|
||||
.map_err(|e| actix_web::error::ErrorInternalServerError(e))?;
|
||||
|
||||
Ok(HttpResponse::build(code).body(html))
|
||||
}
|
||||
|
||||
+15
-14
@@ -1,7 +1,7 @@
|
||||
use actix_web::http::header::IntoHeaderValue;
|
||||
use actix_web::HttpResponse;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::str::FromStr;
|
||||
use std::fmt::{Display, Debug};
|
||||
|
||||
pub fn redirect(path: impl IntoHeaderValue) -> actix_web::Result<HttpResponse> {
|
||||
Ok(HttpResponse::SeeOther()
|
||||
@@ -11,29 +11,30 @@ pub fn redirect(path: impl IntoHeaderValue) -> actix_web::Result<HttpResponse> {
|
||||
|
||||
pub trait ParseOrBadReq {
|
||||
fn parse_or_bad_request<T, E>(&self) -> actix_web::Result<T>
|
||||
where T: FromStr<Err=E>,
|
||||
E: Display + Debug + 'static;
|
||||
where
|
||||
T: FromStr<Err = E>,
|
||||
E: Display + Debug + 'static;
|
||||
}
|
||||
|
||||
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
|
||||
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)
|
||||
})
|
||||
self.parse::<T>().map_err(|e| {
|
||||
error!("Parse error for \"{}\"", self);
|
||||
actix_web::error::ErrorBadRequest(e)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
where
|
||||
T: FromStr<Err = E>,
|
||||
E: Display + Debug + 'static,
|
||||
{
|
||||
self.as_str()
|
||||
.parse_or_bad_request()
|
||||
self.as_str().parse_or_bad_request()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user