diff --git a/Cargo.lock b/Cargo.lock index f951dcf..325b7c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -369,6 +369,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi 0.3.9", +] + [[package]] name = "anyhow" version = "1.0.38" @@ -386,6 +395,17 @@ dependencies = [ "syn", ] +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi 0.3.9", +] + [[package]] name = "autocfg" version = "1.0.1" @@ -612,6 +632,21 @@ dependencies = [ "generic-array 0.14.4", ] +[[package]] +name = "clap" +version = "2.33.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + [[package]] name = "const_fn" version = "0.4.5" @@ -2009,6 +2044,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + [[package]] name = "subtle" version = "2.4.0" @@ -2048,6 +2089,15 @@ dependencies = [ "unic-segment", ] +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.24" @@ -2375,6 +2425,12 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" + [[package]] name = "unicode-xid" version = "0.2.1" @@ -2413,6 +2469,12 @@ dependencies = [ "serde", ] +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + [[package]] name = "version_check" version = "0.9.2" @@ -2588,6 +2650,7 @@ dependencies = [ "actix-web", "actix-web-static-files", "anyhow", + "clap", "heck", "include_dir", "itertools", diff --git a/yopa-store.dat b/yopa-store.dat index 376a481..f72b367 100644 Binary files a/yopa-store.dat and b/yopa-store.dat differ diff --git a/yopa-web/Cargo.toml b/yopa-web/Cargo.toml index 944d1f8..3763052 100644 --- a/yopa-web/Cargo.toml +++ b/yopa-web/Cargo.toml @@ -26,6 +26,7 @@ itertools = "0.10.0" json_dotpath = "1.0.3" anyhow = "1.0.38" thiserror = "1.0.24" +clap = "2" tokio = { version="0.2.6", features=["full"] } diff --git a/yopa-web/src/main.rs b/yopa-web/src/main.rs index fd078f3..2696da4 100644 --- a/yopa-web/src/main.rs +++ b/yopa-web/src/main.rs @@ -7,18 +7,19 @@ extern crate thiserror; use std::collections::HashMap; use std::ops::Deref; +use std::path::PathBuf; use actix_session::CookieSession; -use actix_web::{web, App, HttpResponse, HttpServer}; +use actix_web::{App, HttpResponse, HttpServer, web}; use actix_web_static_files; use actix_web_static_files::ResourceFiles as StaticFiles; +use clap::Arg; use log::LevelFilter; use once_cell::sync::Lazy; use rand::Rng; use tera::Tera; -use yopa::insert::{InsertObj, InsertRel, InsertValue}; -use yopa::{Storage, TypedValue}; +use yopa::{Storage}; use crate::tera_ext::TeraExt; @@ -101,25 +102,83 @@ pub(crate) static TERA: Lazy = Lazy::new(|| { type YopaStoreWrapper = web::Data>; +const DEF_ADDRESS : &str = "127.0.0.1:8080"; + #[actix_web::main] async fn main() -> std::io::Result<()> { - simple_logging::log_to_stderr(LevelFilter::Debug); - + let def_addr_help = format!("Set custom server address, default {}", DEF_ADDRESS); + let app = clap::App::new("yopa") + .arg(Arg::with_name("version") + .long("version") + .short("V") + .help("Show version and exit")) + .arg(Arg::with_name("verbose") + .short("v") + .multiple(true) + .help("Increase verbosity of logging")) + .arg(Arg::with_name("address") + .short("a") + .takes_value(true) + .help(&def_addr_help)) + .arg(Arg::with_name("json") + .long("json") + .short("j") + .help("Use JSON storage format instead of binary")) + .arg(Arg::with_name("file") + .help("Database file to use")); + + let matches = app.get_matches(); + + if matches.is_present("version") { + println!("yopa-web {}, using yopa {}", env!("CARGO_PKG_VERSION"), yopa::VERSION); + return Ok(()); + } + + simple_logging::log_to_stderr(match matches.occurrences_of("verbose") { + 0 => LevelFilter::Warn, + 1 => LevelFilter::Info, + 2 => LevelFilter::Debug, + _ => LevelFilter::Trace, + }); + + debug!("Load Tera templates"); // Ensure the lazy ref is initialized early (to catch template bugs ASAP) let _ = TERA.deref(); - let yopa_store: YopaStoreWrapper = init_yopa() + let file = matches.value_of("file").unwrap_or("yopa-store.dat"); + + let file_path = if file.starts_with('/') { + std::env::current_dir()?.join(file) + } else { + PathBuf::from(file) + }; + + debug!("Using database file: {}", file_path.display()); + + let mut store = if matches.is_present("json") { + Storage::new_json(file_path) + } else { + Storage::new_bincode(file_path) + }; + + store.load() .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + let yopa_store: YopaStoreWrapper = web::Data::new(tokio::sync::RwLock::new(store)); + let mut session_key = [0u8; 32]; rand::thread_rng().fill(&mut session_key); + trace!("Cookie session key: {:?}", session_key); + + let server_address = matches.value_of("address").unwrap_or(DEF_ADDRESS); - debug!("Session key: {:?}", session_key); + println!("Starting web interface at http://{}", server_address); HttpServer::new(move || { let static_files = StaticFiles::new("/static", included_static_files()).do_not_resolve_defaults(); + debug!("Starting server thread"); App::new() /* Middlewares */ .wrap(CookieSession::signed(&session_key).secure(false)) @@ -162,14 +221,7 @@ async fn main() -> std::io::Result<()> { HttpResponse::NotFound().body("File or endpoint not found") })) }) - .bind("127.0.0.1:8080")? - .run() - .await -} - -fn init_yopa() -> anyhow::Result { - let mut store = Storage::new_bincode(std::env::current_dir()?.join("yopa-store.dat")); - store.load()?; - - Ok(web::Data::new(tokio::sync::RwLock::new(store))) + .bind(server_address)? + .run() + .await } diff --git a/yopa-web/src/routes/models/object.rs b/yopa-web/src/routes/models/object.rs index 1c1db65..7e01a13 100644 --- a/yopa-web/src/routes/models/object.rs +++ b/yopa-web/src/routes/models/object.rs @@ -125,15 +125,12 @@ pub(crate) async fn update( #[get("/model/object/delete/{id}")] pub(crate) async fn delete( - id: web::Path, + id: web::Path, store: crate::YopaStoreWrapper, session: Session, ) -> actix_web::Result { 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) { Ok(om) => { wg.persist().err_to_500()?; debug!("Object model deleted, redirecting to root"); diff --git a/yopa-web/src/routes/models/property.rs b/yopa-web/src/routes/models/property.rs index 04e889a..a2c58bb 100644 --- a/yopa-web/src/routes/models/property.rs +++ b/yopa-web/src/routes/models/property.rs @@ -13,7 +13,7 @@ use crate::TERA; #[get("/model/property/create/{object_id}")] pub(crate) async fn create_form( - object_id: web::Path, + id: web::Path, store: crate::YopaStoreWrapper, session: Session, ) -> actix_web::Result { @@ -39,17 +39,16 @@ pub(crate) async fn create_form( ); } - debug!("ID = {}", object_id); + debug!("ID = {}", id); let object = { - let id = object_id.parse_or_bad_request()?; debug!("Create property for ID={}", id); - if let Some(om) = rg.get_object_model(id) { + if let Some(om) = rg.get_object_model(*id) { ObjectOrRelationModelDisplay { id: om.id, describe: format!("object model \"{}\"", om.name), } - } else if let Some(rm) = rg.get_relation_model(id) { + } else if let Some(rm) = rg.get_relation_model(*id) { ObjectOrRelationModelDisplay { id: rm.id, describe: format!("relation model \"{}\"", rm.name), @@ -164,12 +163,12 @@ pub(crate) async fn create( #[get("/model/property/delete/{id}")] pub(crate) async fn delete( - id: web::Path, + id: web::Path, store: crate::YopaStoreWrapper, session: Session, ) -> actix_web::Result { let mut wg = store.write().await; - match wg.undefine_property(id.parse_or_bad_request()?) { + match wg.undefine_property(*id) { Ok(rm) => { wg.persist().err_to_500()?; debug!("Property deleted, redirecting to root"); diff --git a/yopa-web/src/routes/models/relation.rs b/yopa-web/src/routes/models/relation.rs index 2c808c4..101100c 100644 --- a/yopa-web/src/routes/models/relation.rs +++ b/yopa-web/src/routes/models/relation.rs @@ -19,7 +19,7 @@ pub(crate) struct RelationModelDisplay<'a> { #[get("/model/relation/create/{object_id}")] pub(crate) async fn create_form( - object_id: web::Path, + object_id: web::Path, store: crate::YopaStoreWrapper, session: Session, ) -> actix_web::Result { @@ -47,7 +47,7 @@ pub(crate) async fn create_form( } let object = rg - .get_object_model(object_id.parse_or_bad_request()?) + .get_object_model(*object_id) .ok_or_else(|| actix_web::error::ErrorNotFound("No such source object"))?; let mut models: Vec<_> = rg.get_object_models().collect(); @@ -112,12 +112,12 @@ pub(crate) struct ObjectOrRelationModelDisplay { #[get("/model/relation/delete/{id}")] pub(crate) async fn delete( - id: web::Path, + id: web::Path, store: crate::YopaStoreWrapper, session: Session, ) -> actix_web::Result { let mut wg = store.write().await; - match wg.undefine_relation(id.parse_or_bad_request()?) { + match wg.undefine_relation(*id) { Ok(rm) => { wg.persist().err_to_500()?; debug!("Relation deleted, redirecting to root"); diff --git a/yopa-web/src/routes/objects.rs b/yopa-web/src/routes/objects.rs index 0e9882d..ff749b0 100644 --- a/yopa-web/src/routes/objects.rs +++ b/yopa-web/src/routes/objects.rs @@ -499,8 +499,6 @@ pub(crate) async fn update( store: crate::YopaStoreWrapper, session: Session, ) -> actix_web::Result { - warn!("{:?}", form); - let mut wg = store.write().await; let form = form.into_inner(); let name = form.name.clone(); diff --git a/yopa-web/src/utils.rs b/yopa-web/src/utils.rs index 8647dde..a776324 100644 --- a/yopa-web/src/utils.rs +++ b/yopa-web/src/utils.rs @@ -1,5 +1,5 @@ use actix_web::http::header::IntoHeaderValue; -use actix_web::{HttpResponse, ResponseError, Error}; +use actix_web::{HttpResponse, ResponseError}; use std::fmt::{Debug, Display}; use std::str::FromStr; use yopa::StorageError; diff --git a/yopa/src/lib.rs b/yopa/src/lib.rs index 36523d9..9b3843f 100644 --- a/yopa/src/lib.rs +++ b/yopa/src/lib.rs @@ -37,6 +37,8 @@ mod serde_map_as_list; #[cfg(test)] mod tests; +pub const VERSION : &'static str = env!("CARGO_PKG_VERSION"); + /// Stupid storage with naive inefficient file persistence #[derive(Debug, Default, Serialize, Deserialize, Clone)] pub struct Storage {