add persistence
This commit is contained in:
Generated
+17
-4
@@ -448,6 +448,16 @@ version = "0.13.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bincode"
|
||||||
|
version = "1.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f30d3a39baa26f9651f17b375061f3233dde33424a8b72b0dbe93a68a0bc896d"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
@@ -2040,18 +2050,18 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.23"
|
version = "1.0.24"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "76cc616c6abf8c8928e2fdcc0dbfab37175edd8fb49a4641066ad1364fdab146"
|
checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror-impl"
|
name = "thiserror-impl"
|
||||||
version = "1.0.23"
|
version = "1.0.24"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9be73a2caec27583d0046ef3796c3794f868a5bc813db689eed00c7631275cd1"
|
checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -2559,6 +2569,7 @@ name = "yopa"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"bincode",
|
||||||
"itertools",
|
"itertools",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
@@ -2576,6 +2587,7 @@ dependencies = [
|
|||||||
"actix-session",
|
"actix-session",
|
||||||
"actix-web",
|
"actix-web",
|
||||||
"actix-web-static-files",
|
"actix-web-static-files",
|
||||||
|
"anyhow",
|
||||||
"heck",
|
"heck",
|
||||||
"include_dir",
|
"include_dir",
|
||||||
"itertools",
|
"itertools",
|
||||||
@@ -2588,6 +2600,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"simple-logging",
|
"simple-logging",
|
||||||
"tera",
|
"tera",
|
||||||
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
"yopa",
|
"yopa",
|
||||||
]
|
]
|
||||||
|
|||||||
Binary file not shown.
+3
-1
@@ -8,7 +8,7 @@ build = "build.rs"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
yopa = { path = "../yopa", features = [] }
|
yopa = { path = "../yopa", features = ["uuid-ids"] }
|
||||||
serde = "1"
|
serde = "1"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
heck = "0.3.2"
|
heck = "0.3.2"
|
||||||
@@ -24,6 +24,8 @@ once_cell = "1.5.2"
|
|||||||
rand = "0.8.3"
|
rand = "0.8.3"
|
||||||
itertools = "0.10.0"
|
itertools = "0.10.0"
|
||||||
json_dotpath = "1.0.3"
|
json_dotpath = "1.0.3"
|
||||||
|
anyhow = "1.0.38"
|
||||||
|
thiserror = "1.0.24"
|
||||||
|
|
||||||
tokio = { version="0.2.6", features=["full"] }
|
tokio = { version="0.2.6", features=["full"] }
|
||||||
|
|
||||||
|
|||||||
@@ -23,8 +23,8 @@ export function objCopy(object) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function castId(id) {
|
export function castId(id) {
|
||||||
// TODO no-op after switching to UUIDs
|
return id.toString();
|
||||||
return +id;
|
//return +id;
|
||||||
}
|
}
|
||||||
|
|
||||||
// like _.isEmpty, but less stupid
|
// like _.isEmpty, but less stupid
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+8
-129
@@ -2,6 +2,8 @@
|
|||||||
extern crate actix_web;
|
extern crate actix_web;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate thiserror;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
@@ -106,7 +108,8 @@ async fn main() -> std::io::Result<()> {
|
|||||||
// Ensure the lazy ref is initialized early (to catch template bugs ASAP)
|
// Ensure the lazy ref is initialized early (to catch template bugs ASAP)
|
||||||
let _ = TERA.deref();
|
let _ = TERA.deref();
|
||||||
|
|
||||||
let yopa_store: YopaStoreWrapper = init_yopa();
|
let yopa_store: YopaStoreWrapper = init_yopa()
|
||||||
|
.map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?;
|
||||||
|
|
||||||
let mut session_key = [0u8; 32];
|
let mut session_key = [0u8; 32];
|
||||||
rand::thread_rng().fill(&mut session_key);
|
rand::thread_rng().fill(&mut session_key);
|
||||||
@@ -164,133 +167,9 @@ async fn main() -> std::io::Result<()> {
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
fn init_yopa() -> YopaStoreWrapper {
|
fn init_yopa() -> anyhow::Result<YopaStoreWrapper> {
|
||||||
let mut store = Storage::new();
|
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)))
|
||||||
|
|
||||||
// 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(),
|
|
||||||
})
|
|
||||||
.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 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();
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
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();
|
|
||||||
|
|
||||||
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 {
|
|
||||||
model: val_descr,
|
|
||||||
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();
|
|
||||||
*/
|
|
||||||
|
|
||||||
web::Data::new(tokio::sync::RwLock::new(store))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use actix_session::Session;
|
use actix_session::Session;
|
||||||
use actix_web::{web, Responder};
|
use actix_web::{web, Responder, HttpResponse};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use yopa::model::{ObjectModel, PropertyModel};
|
use yopa::model::{ObjectModel, PropertyModel};
|
||||||
@@ -8,7 +8,7 @@ use yopa::ID;
|
|||||||
use crate::routes::models::relation::RelationModelDisplay;
|
use crate::routes::models::relation::RelationModelDisplay;
|
||||||
use crate::session_ext::SessionExt;
|
use crate::session_ext::SessionExt;
|
||||||
use crate::tera_ext::TeraExt;
|
use crate::tera_ext::TeraExt;
|
||||||
use crate::utils::redirect;
|
use crate::utils::{redirect, StorageErrorIntoResponseError};
|
||||||
use crate::TERA;
|
use crate::TERA;
|
||||||
|
|
||||||
#[derive(Serialize, Debug)]
|
#[derive(Serialize, Debug)]
|
||||||
@@ -45,7 +45,7 @@ pub(crate) async fn create(
|
|||||||
form: web::Form<ObjectModelForm>,
|
form: web::Form<ObjectModelForm>,
|
||||||
store: crate::YopaStoreWrapper,
|
store: crate::YopaStoreWrapper,
|
||||||
session: Session,
|
session: Session,
|
||||||
) -> actix_web::Result<impl Responder> {
|
) -> actix_web::Result<HttpResponse> {
|
||||||
let mut wg = store.write().await;
|
let mut wg = store.write().await;
|
||||||
let form = form.into_inner();
|
let form = form.into_inner();
|
||||||
match wg.define_object(ObjectModel {
|
match wg.define_object(ObjectModel {
|
||||||
@@ -53,6 +53,7 @@ pub(crate) async fn create(
|
|||||||
name: form.name.clone(),
|
name: form.name.clone(),
|
||||||
}) {
|
}) {
|
||||||
Ok(_id) => {
|
Ok(_id) => {
|
||||||
|
wg.persist().err_to_500()?;
|
||||||
debug!("Object created, redirecting to root");
|
debug!("Object created, redirecting to root");
|
||||||
session.flash_success(format!("Object model \"{}\" created.", form.name));
|
session.flash_success(format!("Object model \"{}\" created.", form.name));
|
||||||
redirect("/models")
|
redirect("/models")
|
||||||
@@ -108,6 +109,7 @@ pub(crate) async fn update(
|
|||||||
name: form.name.clone(),
|
name: form.name.clone(),
|
||||||
}) {
|
}) {
|
||||||
Ok(_id) => {
|
Ok(_id) => {
|
||||||
|
wg.persist().err_to_500()?;
|
||||||
debug!("Object updated, redirecting to root");
|
debug!("Object updated, redirecting to root");
|
||||||
session.flash_success(format!("Object model \"{}\" updated.", form.name));
|
session.flash_success(format!("Object model \"{}\" updated.", form.name));
|
||||||
redirect("/models")
|
redirect("/models")
|
||||||
@@ -133,6 +135,7 @@ pub(crate) async fn delete(
|
|||||||
.map_err(|e| actix_web::error::ErrorBadRequest(e))?,
|
.map_err(|e| actix_web::error::ErrorBadRequest(e))?,
|
||||||
) {
|
) {
|
||||||
Ok(om) => {
|
Ok(om) => {
|
||||||
|
wg.persist().err_to_500()?;
|
||||||
debug!("Object model deleted, redirecting to root");
|
debug!("Object model deleted, redirecting to root");
|
||||||
session.flash_success(format!("Object model \"{}\" deleted.", om.name));
|
session.flash_success(format!("Object model \"{}\" deleted.", om.name));
|
||||||
redirect("/models")
|
redirect("/models")
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use yopa::{DataType, TypedValue, ID};
|
|||||||
use crate::routes::models::relation::ObjectOrRelationModelDisplay;
|
use crate::routes::models::relation::ObjectOrRelationModelDisplay;
|
||||||
use crate::session_ext::SessionExt;
|
use crate::session_ext::SessionExt;
|
||||||
use crate::tera_ext::TeraExt;
|
use crate::tera_ext::TeraExt;
|
||||||
use crate::utils::{redirect, ParseOrBadReq};
|
use crate::utils::{redirect, ParseOrBadReq, StorageErrorIntoResponseError};
|
||||||
use crate::TERA;
|
use crate::TERA;
|
||||||
|
|
||||||
#[get("/model/property/create/{object_id}")]
|
#[get("/model/property/create/{object_id}")]
|
||||||
@@ -148,6 +148,7 @@ pub(crate) async fn create(
|
|||||||
default,
|
default,
|
||||||
}) {
|
}) {
|
||||||
Ok(_id) => {
|
Ok(_id) => {
|
||||||
|
wg.persist().err_to_500()?;
|
||||||
debug!("Property created, redirecting to root");
|
debug!("Property created, redirecting to root");
|
||||||
session.flash_success(format!("Property model \"{}\" created.", form.name));
|
session.flash_success(format!("Property model \"{}\" created.", form.name));
|
||||||
redirect("/models")
|
redirect("/models")
|
||||||
@@ -170,6 +171,7 @@ pub(crate) async fn delete(
|
|||||||
let mut wg = store.write().await;
|
let mut wg = store.write().await;
|
||||||
match wg.undefine_property(id.parse_or_bad_request()?) {
|
match wg.undefine_property(id.parse_or_bad_request()?) {
|
||||||
Ok(rm) => {
|
Ok(rm) => {
|
||||||
|
wg.persist().err_to_500()?;
|
||||||
debug!("Property deleted, redirecting to root");
|
debug!("Property deleted, redirecting to root");
|
||||||
session.flash_success(format!("Property \"{}\" deleted.", rm.name));
|
session.flash_success(format!("Property \"{}\" deleted.", rm.name));
|
||||||
redirect("/models")
|
redirect("/models")
|
||||||
@@ -256,7 +258,8 @@ pub(crate) async fn update(
|
|||||||
default,
|
default,
|
||||||
}) {
|
}) {
|
||||||
Ok(_id) => {
|
Ok(_id) => {
|
||||||
debug!("Relation updated, redirecting to root");
|
wg.persist().err_to_500()?;
|
||||||
|
debug!("Property updated, redirecting to root");
|
||||||
session.flash_success(format!("Property \"{}\" updated.", form.name));
|
session.flash_success(format!("Property \"{}\" updated.", form.name));
|
||||||
redirect("/models")
|
redirect("/models")
|
||||||
}
|
}
|
||||||
@@ -264,7 +267,7 @@ pub(crate) async fn update(
|
|||||||
warn!("Error updating model: {}", e);
|
warn!("Error updating model: {}", e);
|
||||||
session.flash_error(e.to_string());
|
session.flash_error(e.to_string());
|
||||||
session.set("old", form).unwrap();
|
session.set("old", form).unwrap();
|
||||||
redirect(format!("/model/relation/update/{}", id))
|
redirect(format!("/model/property/update/{}", id))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use yopa::ID;
|
|||||||
|
|
||||||
use crate::session_ext::SessionExt;
|
use crate::session_ext::SessionExt;
|
||||||
use crate::tera_ext::TeraExt;
|
use crate::tera_ext::TeraExt;
|
||||||
use crate::utils::{redirect, ParseOrBadReq};
|
use crate::utils::{redirect, ParseOrBadReq, StorageErrorIntoResponseError};
|
||||||
use crate::TERA;
|
use crate::TERA;
|
||||||
|
|
||||||
#[derive(Serialize, Debug)]
|
#[derive(Serialize, Debug)]
|
||||||
@@ -90,6 +90,7 @@ pub(crate) async fn create(
|
|||||||
related: form.related,
|
related: form.related,
|
||||||
}) {
|
}) {
|
||||||
Ok(_id) => {
|
Ok(_id) => {
|
||||||
|
wg.persist().err_to_500()?;
|
||||||
debug!("Relation created, redirecting to root");
|
debug!("Relation created, redirecting to root");
|
||||||
session.flash_success(format!("Relation model \"{}\" created.", form.name));
|
session.flash_success(format!("Relation model \"{}\" created.", form.name));
|
||||||
redirect("/models")
|
redirect("/models")
|
||||||
@@ -118,6 +119,7 @@ pub(crate) async fn delete(
|
|||||||
let mut wg = store.write().await;
|
let mut wg = store.write().await;
|
||||||
match wg.undefine_relation(id.parse_or_bad_request()?) {
|
match wg.undefine_relation(id.parse_or_bad_request()?) {
|
||||||
Ok(rm) => {
|
Ok(rm) => {
|
||||||
|
wg.persist().err_to_500()?;
|
||||||
debug!("Relation deleted, redirecting to root");
|
debug!("Relation deleted, redirecting to root");
|
||||||
session.flash_success(format!("Relation model \"{}\" deleted.", rm.name));
|
session.flash_success(format!("Relation model \"{}\" deleted.", rm.name));
|
||||||
redirect("/models")
|
redirect("/models")
|
||||||
@@ -189,6 +191,7 @@ pub(crate) async fn update(
|
|||||||
related: Default::default(), // dummy
|
related: Default::default(), // dummy
|
||||||
}) {
|
}) {
|
||||||
Ok(_id) => {
|
Ok(_id) => {
|
||||||
|
wg.persist().err_to_500()?;
|
||||||
debug!("Relation updated, redirecting to root");
|
debug!("Relation updated, redirecting to root");
|
||||||
session.flash_success(format!("Relation model \"{}\" updated.", form.name));
|
session.flash_success(format!("Relation model \"{}\" updated.", form.name));
|
||||||
redirect("/models")
|
redirect("/models")
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use actix_session::Session;
|
|||||||
use actix_web::{web, HttpResponse, Responder};
|
use actix_web::{web, HttpResponse, Responder};
|
||||||
|
|
||||||
use crate::tera_ext::TeraExt;
|
use crate::tera_ext::TeraExt;
|
||||||
use crate::utils::redirect;
|
use crate::utils::{redirect, StorageErrorIntoResponseError};
|
||||||
use crate::TERA;
|
use crate::TERA;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use yopa::data::Object;
|
use yopa::data::Object;
|
||||||
@@ -110,6 +110,7 @@ pub(crate) async fn create(
|
|||||||
|
|
||||||
match wg.insert_object(form) {
|
match wg.insert_object(form) {
|
||||||
Ok(_id) => {
|
Ok(_id) => {
|
||||||
|
wg.persist().err_to_500()?;
|
||||||
debug!("Object created, redirecting to root");
|
debug!("Object created, redirecting to root");
|
||||||
session.flash_success(format!("{} \"{}\" created.", model_name, name));
|
session.flash_success(format!("{} \"{}\" created.", model_name, name));
|
||||||
Ok(HttpResponse::Ok().finish())
|
Ok(HttpResponse::Ok().finish())
|
||||||
@@ -512,6 +513,7 @@ pub(crate) async fn update(
|
|||||||
|
|
||||||
match wg.update_object(form) {
|
match wg.update_object(form) {
|
||||||
Ok(_id) => {
|
Ok(_id) => {
|
||||||
|
wg.persist().err_to_500()?;
|
||||||
debug!("Object created, redirecting to root");
|
debug!("Object created, redirecting to root");
|
||||||
session.flash_success(format!("{} \"{}\" updated.", model_name, name));
|
session.flash_success(format!("{} \"{}\" updated.", model_name, name));
|
||||||
Ok(HttpResponse::Ok().finish())
|
Ok(HttpResponse::Ok().finish())
|
||||||
@@ -532,6 +534,7 @@ pub(crate) async fn delete(
|
|||||||
let mut wg = store.write().await;
|
let mut wg = store.write().await;
|
||||||
match wg.delete_object(*id) {
|
match wg.delete_object(*id) {
|
||||||
Ok(obj) => {
|
Ok(obj) => {
|
||||||
|
wg.persist().err_to_500()?;
|
||||||
debug!("Object deleted, redirecting to root");
|
debug!("Object deleted, redirecting to root");
|
||||||
session.flash_success(format!("Object \"{}\" deleted.", obj.name));
|
session.flash_success(format!("Object \"{}\" deleted.", obj.name));
|
||||||
redirect("/")
|
redirect("/")
|
||||||
|
|||||||
+22
-1
@@ -1,7 +1,8 @@
|
|||||||
use actix_web::http::header::IntoHeaderValue;
|
use actix_web::http::header::IntoHeaderValue;
|
||||||
use actix_web::HttpResponse;
|
use actix_web::{HttpResponse, ResponseError, Error};
|
||||||
use std::fmt::{Debug, Display};
|
use std::fmt::{Debug, Display};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
use yopa::StorageError;
|
||||||
|
|
||||||
pub fn redirect(path: impl IntoHeaderValue) -> actix_web::Result<HttpResponse> {
|
pub fn redirect(path: impl IntoHeaderValue) -> actix_web::Result<HttpResponse> {
|
||||||
Ok(HttpResponse::SeeOther()
|
Ok(HttpResponse::SeeOther()
|
||||||
@@ -38,3 +39,23 @@ impl ParseOrBadReq for String {
|
|||||||
self.as_str().parse_or_bad_request()
|
self.as_str().parse_or_bad_request()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum ActixErrorWrapper {
|
||||||
|
#[error(transparent)]
|
||||||
|
Storage(#[from] StorageError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResponseError for ActixErrorWrapper {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait StorageErrorIntoResponseError<T> {
|
||||||
|
fn err_to_500(self) -> Result<T, ActixErrorWrapper>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> StorageErrorIntoResponseError<T> for Result<T, StorageError> {
|
||||||
|
fn err_to_500(self) -> Result<T, ActixErrorWrapper> {
|
||||||
|
self.map_err(|e| e.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ anyhow = "1.0.38"
|
|||||||
thiserror = "1.0.23"
|
thiserror = "1.0.23"
|
||||||
itertools = "0.10.0"
|
itertools = "0.10.0"
|
||||||
lazy_static = "1.4.0"
|
lazy_static = "1.4.0"
|
||||||
|
bincode = "1.3.1"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
|
|||||||
+121
-2
@@ -22,6 +22,9 @@ use crate::data::Object;
|
|||||||
use crate::update::{UpdateObj, UpsertValue};
|
use crate::update::{UpdateObj, UpsertValue};
|
||||||
pub use data::TypedValue;
|
pub use data::TypedValue;
|
||||||
pub use model::DataType;
|
pub use model::DataType;
|
||||||
|
use std::path::{PathBuf, Path};
|
||||||
|
use std::fs::OpenOptions;
|
||||||
|
use std::io::{BufReader, BufWriter};
|
||||||
|
|
||||||
mod cool;
|
mod cool;
|
||||||
pub mod data;
|
pub mod data;
|
||||||
@@ -34,7 +37,7 @@ mod serde_map_as_list;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
/// Stupid storage with no persistence
|
/// Stupid storage with naive inefficient file persistence
|
||||||
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Default, Serialize, Deserialize, Clone)]
|
||||||
pub struct Storage {
|
pub struct Storage {
|
||||||
#[serde(with = "serde_map_as_list")]
|
#[serde(with = "serde_map_as_list")]
|
||||||
@@ -50,14 +53,46 @@ pub struct Storage {
|
|||||||
relations: HashMap<ID, data::Relation>,
|
relations: HashMap<ID, data::Relation>,
|
||||||
#[serde(with = "serde_map_as_list")]
|
#[serde(with = "serde_map_as_list")]
|
||||||
values: HashMap<ID, data::Value>,
|
values: HashMap<ID, data::Value>,
|
||||||
|
|
||||||
|
#[serde(skip)]
|
||||||
|
opts: StoreOpts,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct StoreOpts {
|
||||||
|
file : Option<PathBuf>,
|
||||||
|
file_format: FileEncoding
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for StoreOpts {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
file: None,
|
||||||
|
file_format: FileEncoding::JSON
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug,Clone,Copy)]
|
||||||
|
pub enum FileEncoding {
|
||||||
|
JSON,
|
||||||
|
BINCODE,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug,Error)]
|
||||||
pub enum StorageError {
|
pub enum StorageError {
|
||||||
#[error("Referenced {0} does not exist")]
|
#[error("Referenced {0} does not exist")]
|
||||||
NotExist(Cow<'static, str>),
|
NotExist(Cow<'static, str>),
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
ConstraintViolation(Cow<'static, str>),
|
ConstraintViolation(Cow<'static, str>),
|
||||||
|
#[error("Persistence not configured!")]
|
||||||
|
NotPersistent,
|
||||||
|
#[error(transparent)]
|
||||||
|
IO(#[from] std::io::Error),
|
||||||
|
#[error(transparent)]
|
||||||
|
SerdeJson(#[from] serde_json::Error),
|
||||||
|
#[error(transparent)]
|
||||||
|
Bincode(#[from] bincode::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Storage {
|
impl Storage {
|
||||||
@@ -66,6 +101,90 @@ impl Storage {
|
|||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_json(file : impl AsRef<Path>) -> Self {
|
||||||
|
let mut s = Self::new();
|
||||||
|
s.opts.file_format = FileEncoding::JSON;
|
||||||
|
s.opts.file = Some(file.as_ref().to_path_buf());
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_bincode(file : impl AsRef<Path>) -> Self {
|
||||||
|
let mut s = Self::new();
|
||||||
|
s.opts.file_format = FileEncoding::BINCODE;
|
||||||
|
s.opts.file = Some(file.as_ref().to_path_buf());
|
||||||
|
s
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_file(&mut self, file : impl AsRef<Path>, format : FileEncoding) {
|
||||||
|
self.opts.file_format = format;
|
||||||
|
self.opts.file = Some(file.as_ref().to_path_buf());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unset_file(&mut self) {
|
||||||
|
self.opts.file = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load(&mut self) -> Result<(), StorageError> {
|
||||||
|
match &self.opts.file {
|
||||||
|
None => {
|
||||||
|
return Err(StorageError::NotPersistent);
|
||||||
|
}
|
||||||
|
Some(path) => {
|
||||||
|
debug!("Load from: {}", path.display());
|
||||||
|
|
||||||
|
if !path.exists() {
|
||||||
|
warn!("File does not exist, skip load.");
|
||||||
|
return Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
let f = OpenOptions::new().read(true).open(&path)?;
|
||||||
|
|
||||||
|
let reader = BufReader::new(f);
|
||||||
|
|
||||||
|
let parsed : Self = match self.opts.file_format {
|
||||||
|
FileEncoding::JSON => {
|
||||||
|
serde_json::from_reader(reader)?
|
||||||
|
}
|
||||||
|
FileEncoding::BINCODE => {
|
||||||
|
bincode::deserialize_from(reader)?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let opts = std::mem::replace(&mut self.opts, StoreOpts::default());
|
||||||
|
*self = parsed;
|
||||||
|
self.opts = opts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn persist(&mut self) -> Result<(), StorageError> {
|
||||||
|
match &self.opts.file {
|
||||||
|
None => {
|
||||||
|
warn!("Store is not persistent!");
|
||||||
|
//return Err(StorageError::NotPersistent);
|
||||||
|
}
|
||||||
|
Some(path) => {
|
||||||
|
debug!("Persist to: {}", path.display());
|
||||||
|
|
||||||
|
let f = OpenOptions::new().write(true).create(true).truncate(true).open(&path)?;
|
||||||
|
let writer = BufWriter::new(f);
|
||||||
|
|
||||||
|
match self.opts.file_format {
|
||||||
|
FileEncoding::JSON => {
|
||||||
|
serde_json::to_writer(writer, self)?;
|
||||||
|
}
|
||||||
|
FileEncoding::BINCODE => {
|
||||||
|
bincode::serialize_into(writer, self)?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Define a data object
|
/// Define a data object
|
||||||
pub fn define_object(&mut self, mut tpl: model::ObjectModel) -> Result<ID, StorageError> {
|
pub fn define_object(&mut self, mut tpl: model::ObjectModel) -> Result<ID, StorageError> {
|
||||||
if tpl.name.is_empty() {
|
if tpl.name.is_empty() {
|
||||||
|
|||||||
Reference in New Issue
Block a user