a small relational database with user-editable schema for manual data entry
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
yopa/yopa-web/src/main.rs

155 lines
4.7 KiB

#[macro_use] extern crate log;
#[macro_use] extern crate actix_web;
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder, HttpRequest};
use parking_lot::Mutex;
use actix_web::web::{service, scope};
use actix_web::http::StatusCode;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::path::{PathBuf, Path};
use actix_web_static_files;
use tera::Tera;
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"));
// Embed templates
static TEMPLATES: include_dir::Dir = include_dir::include_dir!("./resources/templates");
pub(crate) static TERA : Lazy<Tera> = Lazy::new(|| {
let mut tera = Tera::default();
tera.add_include_dir_templates(&TEMPLATES);
// 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"))
});
tera
});
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()
.app_data(database.clone())
.service(routes::index)
.service(static_files)
})
.bind("127.0.0.1:8080")?.run().await
}