#[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 = 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| -> tera::Result { 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>; #[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 }