show model overview in index page
This commit is contained in:
+115
-17
@@ -1,5 +1,5 @@
|
||||
#[macro_use] extern crate log;
|
||||
#[macro_use] extern crate lazy_static;
|
||||
#[macro_use] extern crate actix_web;
|
||||
|
||||
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder, HttpRequest};
|
||||
use parking_lot::Mutex;
|
||||
@@ -13,8 +13,14 @@ use include_dir::Dir;
|
||||
use std::sync::Arc;
|
||||
use log::LevelFilter;
|
||||
use crate::tera_ext::TeraExt;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::borrow::Borrow;
|
||||
use std::ops::Deref;
|
||||
use yopa::{Storage, TypedValue};
|
||||
use std::collections::HashMap;
|
||||
|
||||
mod tera_ext;
|
||||
mod routes;
|
||||
|
||||
// Embed static files
|
||||
include!(concat!(env!("OUT_DIR"), "/static_files.rs"));
|
||||
@@ -22,35 +28,127 @@ include!(concat!(env!("OUT_DIR"), "/static_files.rs"));
|
||||
// Embed templates
|
||||
static TEMPLATES: include_dir::Dir = include_dir::include_dir!("./resources/templates");
|
||||
|
||||
lazy_static! {
|
||||
static ref TERA : Tera = {
|
||||
let mut tera = Tera::default();
|
||||
tera.add_include_dir_templates(&TEMPLATES);
|
||||
tera
|
||||
};
|
||||
}
|
||||
pub(crate) static TERA : Lazy<Tera> = Lazy::new(|| {
|
||||
let mut tera = Tera::default();
|
||||
tera.add_include_dir_templates(&TEMPLATES);
|
||||
|
||||
#[get("/")]
|
||||
async fn service_index(req: HttpRequest) -> actix_web::Result<impl Responder> {
|
||||
let mut context = tera::Context::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() {
|
||||
return Ok(v.clone());
|
||||
}
|
||||
}
|
||||
Err(tera::Error::msg("Expected nonenmpty object"))
|
||||
});
|
||||
|
||||
let html = TERA.render("index", &context).map_err(|e| {
|
||||
actix_web::error::ErrorInternalServerError(e)
|
||||
})?;
|
||||
tera
|
||||
});
|
||||
|
||||
Ok(HttpResponse::Ok().body(html))
|
||||
}
|
||||
type YopaStoreWrapper = web::Data<tokio::sync::RwLock<yopa::Storage>>;
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
simple_logging::log_to_stderr(LevelFilter::Debug);
|
||||
|
||||
// Ensure the lazy ref is initialized early (to catch template bugs ASAP)
|
||||
let _ = TERA.deref();
|
||||
|
||||
let database : YopaStoreWrapper = {
|
||||
let mut store = Storage::new();
|
||||
|
||||
// Seed the store with some dummy data for view development
|
||||
use yopa::model;
|
||||
use yopa::DataType;
|
||||
|
||||
let id_recipe = store.define_object(model::ObjectModel {
|
||||
id: Default::default(),
|
||||
name: "Recipe".to_string(),
|
||||
parent: None
|
||||
}).unwrap();
|
||||
let id_book = store.define_object(model::ObjectModel {
|
||||
id: Default::default(),
|
||||
name: "Book".to_string(),
|
||||
parent: None
|
||||
}).unwrap();
|
||||
let id_ing = store.define_object(model::ObjectModel {
|
||||
id: Default::default(),
|
||||
name: "Ingredient".to_string(),
|
||||
parent: None
|
||||
}).unwrap();
|
||||
|
||||
store.define_property(model::PropertyModel {
|
||||
id: Default::default(),
|
||||
object: id_recipe,
|
||||
name: "name".to_string(),
|
||||
optional: false,
|
||||
multiple: true,
|
||||
data_type: DataType::String,
|
||||
default: None
|
||||
}).unwrap();
|
||||
|
||||
store.define_property(model::PropertyModel {
|
||||
id: Default::default(),
|
||||
object: id_book,
|
||||
name: "title".to_string(),
|
||||
optional: false,
|
||||
multiple: false,
|
||||
data_type: DataType::String,
|
||||
default: None
|
||||
}).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: Some(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(),
|
||||
optional: true,
|
||||
multiple: true,
|
||||
related: id_book
|
||||
}).unwrap();
|
||||
|
||||
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: None
|
||||
}).unwrap();
|
||||
|
||||
store.define_relation(model::RelationModel {
|
||||
id: Default::default(),
|
||||
object: id_recipe,
|
||||
name: "related recipe".to_string(),
|
||||
optional: true,
|
||||
multiple: true,
|
||||
related: id_recipe
|
||||
}).unwrap();
|
||||
|
||||
web::Data::new(tokio::sync::RwLock::new(store))
|
||||
};
|
||||
|
||||
HttpServer::new(move || {
|
||||
let static_files = actix_web_static_files::ResourceFiles::new("/static", included_static_files())
|
||||
.do_not_resolve_defaults();
|
||||
|
||||
App::new()
|
||||
.service(service_index)
|
||||
.app_data(database.clone())
|
||||
.service(routes::index)
|
||||
.service(static_files)
|
||||
})
|
||||
.bind("127.0.0.1:8080")?.run().await
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
use actix_web::{web, HttpRequest, Responder};
|
||||
use crate::TERA;
|
||||
use crate::tera_ext::TeraExt;
|
||||
use yopa::Storage;
|
||||
use serde::Serialize;
|
||||
use yopa::model::{PropertyModel, RelationModel};
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
struct ObjectModelDisplay<'a> {
|
||||
id : yopa::ID,
|
||||
name : &'a str,
|
||||
properties: Vec<&'a PropertyModel>,
|
||||
relations: Vec<RelationModelDisplay<'a>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
struct RelationModelDisplay<'a> {
|
||||
model : &'a RelationModel,
|
||||
related_name : &'a str,
|
||||
properties: Vec<&'a PropertyModel>,
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
pub(crate) async fn index(req: HttpRequest, store : crate::YopaStoreWrapper) -> actix_web::Result<impl Responder> {
|
||||
|
||||
let rg = store.read().await;
|
||||
|
||||
let models_iter = rg.get_object_models();
|
||||
|
||||
// object and relation props
|
||||
let mut model_props = rg.get_grouped_prop_models();
|
||||
|
||||
let mut model_relations = rg.get_grouped_relation_models();
|
||||
|
||||
let mut models = vec![];
|
||||
for om in models_iter {
|
||||
let mut oprops = model_props.remove(&om.id).unwrap_or_default();
|
||||
let mut relations = model_relations.remove(&om.id).unwrap_or_default();
|
||||
|
||||
let rel_displays = relations.into_iter().map(|rm| {
|
||||
let mut rprops = model_props.remove(&rm.id).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<_>>();
|
||||
|
||||
oprops.sort_by_key(|m| &m.name);
|
||||
|
||||
models.push(ObjectModelDisplay {
|
||||
id: om.id,
|
||||
name: &om.name,
|
||||
properties: oprops,
|
||||
relations: rel_displays,
|
||||
})
|
||||
}
|
||||
|
||||
models.sort_by_key(|m| m.name);
|
||||
|
||||
let mut ctx = tera::Context::new();
|
||||
ctx.insert("models", &models);
|
||||
|
||||
TERA.build_response("index", &ctx)
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
use tera::Tera;
|
||||
use include_dir::Dir;
|
||||
use actix_web::HttpResponse;
|
||||
use actix_web::http::StatusCode;
|
||||
|
||||
fn tera_includedir_walk_folder_inner(collected : &mut Vec<(String, String)>, tera : &mut Tera, dir: &Dir) {
|
||||
for f in dir.files() {
|
||||
@@ -27,13 +29,27 @@ fn tera_includedir_walk_folder_inner(collected : &mut Vec<(String, String)>, ter
|
||||
|
||||
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> {
|
||||
self.build_err_response(StatusCode::OK, template, context)
|
||||
}
|
||||
|
||||
fn build_err_response(&self, code : StatusCode, template: &str, context : &tera::Context) -> actix_web::Result<HttpResponse>;
|
||||
}
|
||||
|
||||
impl TeraExt for Tera {
|
||||
fn add_include_dir_templates(&mut self, dir: &Dir) -> tera::Result<()> {
|
||||
debug!("Walk dir: {:?}", dir);
|
||||
debug!("Walk dir: {:?}", dir.path());
|
||||
let mut templates = vec![];
|
||||
tera_includedir_walk_folder_inner(&mut templates, self, dir);
|
||||
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)
|
||||
})?;
|
||||
|
||||
Ok(HttpResponse::build(code).body(html))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user