fix warning and reformat all

This commit is contained in:
2021-02-20 18:52:59 +01:00
parent 6229f7969c
commit 2a93800765
19 changed files with 1103 additions and 612 deletions
+2 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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")]
+36 -27
View File
@@ -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();
+11 -8
View File
@@ -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));
+40 -38
View File
@@ -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))
}
}
+25 -20
View File
@@ -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
View File
@@ -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();
+33 -8
View File
@@ -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
View File
@@ -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()
}
}