Compare commits

...

3 Commits

  1. 105
      src/main.rs
  2. 47
      src/store/form.rs
  3. 60
      src/store/mod.rs
  4. 1
      src/store/model.rs

@ -5,7 +5,7 @@ extern crate rocket;
#[macro_use] #[macro_use]
extern crate serde; extern crate serde;
use serde_json::{json, Value, Number}; use serde_json::Value;
//use rocket::request::FromSegments; //use rocket::request::FromSegments;
//use rocket::http::uri::Segments; //use rocket::http::uri::Segments;
@ -14,17 +14,16 @@ use rocket_contrib::templates::Template;
mod store; mod store;
use crate::store::form::{render_card_fields, render_empty_fields, RenderedCard, RenderedField};
use crate::store::model::FieldKind;
use crate::store::Store; use crate::store::Store;
use rocket::{State, Data}; use indexmap::map::IndexMap;
use parking_lot::RwLock; use parking_lot::RwLock;
use rocket::request::{Form, FormItems, FromForm};
use rocket::response::Redirect; use rocket::response::Redirect;
use rocket::http::Status; use rocket::State;
use rocket::request::{Form, LenientForm, FromForm, FormItems};
use std::env; use std::env;
use crate::store::form::{RenderedField, render_empty_fields, RenderedCard, render_card_fields};
use crate::store::model::FieldKind;
use std::ops::Deref;
use indexmap::map::IndexMap;
#[derive(Serialize, Debug)] #[derive(Serialize, Debug)]
pub struct ListContext<'a> { pub struct ListContext<'a> {
@ -34,25 +33,45 @@ pub struct ListContext<'a> {
pub pages: usize, pub pages: usize,
} }
const per_page: usize = 20;
fn find_page_with_card(store: &Store, card_id: usize) -> Option<usize> {
if let Some((n, _)) = store
.data
.cards
.iter()
.enumerate()
.find(|(_n, (id, _card))| **id == card_id)
{
Some(n / per_page)
} else {
None
}
}
#[get("/?<page>&<card>")] #[get("/?<page>&<card>")]
fn route_index(store: State<RwLock<Store>>, page: Option<usize>, card: Option<usize>) -> Template { fn route_index(store: State<RwLock<Store>>, page: Option<usize>, card: Option<usize>) -> Template {
let rg = store.read(); let rg = store.read();
let per_page: usize = 20;
let mut page = page.unwrap_or_default(); let mut page = page.unwrap_or_default();
let n_pages = (rg.data.cards.len() as f64 / per_page as f64).ceil() as usize; let n_pages = (rg.data.cards.len() as f64 / per_page as f64).ceil() as usize;
if let Some(card_id) = card { if let Some(card_id) = card {
if let Some((n, _)) = rg.data.cards.iter().enumerate().find(|(_n, (id, _card))| **id == card_id) { page = find_page_with_card(&rg, card_id).unwrap_or(page);
page = n / per_page; }
}
if page >= n_pages {
page = n_pages - 1;
} }
let context = ListContext { let context = ListContext {
fields: render_empty_fields(&rg), fields: render_empty_fields(&rg),
pages: n_pages, pages: n_pages,
page, page,
cards: rg.data.cards.iter() cards: rg
.data
.cards
.iter()
.skip(page * per_page) .skip(page * per_page)
.take(per_page) .take(per_page)
.filter_map(|(id, card)| { .filter_map(|(id, card)| {
@ -64,7 +83,8 @@ fn route_index(store: State<RwLock<Store>>, page: Option<usize>, card: Option<us
} else { } else {
None None
} }
}).collect(), })
.collect(),
}; };
Template::render("index", context) Template::render("index", context)
@ -72,7 +92,7 @@ fn route_index(store: State<RwLock<Store>>, page: Option<usize>, card: Option<us
#[derive(Default)] #[derive(Default)]
struct MapFromForm { struct MapFromForm {
pub data: IndexMap<String, String> pub data: IndexMap<String, String>,
} }
impl<'a> FromForm<'a> for MapFromForm { impl<'a> FromForm<'a> for MapFromForm {
@ -88,7 +108,7 @@ impl<'a> FromForm<'a> for MapFromForm {
} }
} }
fn collect_card_form(store: &Store, mut form: MapFromForm) -> IndexMap::<String, Value> { fn collect_card_form(store: &Store, mut form: MapFromForm) -> IndexMap<String, Value> {
let mut card = IndexMap::new(); let mut card = IndexMap::new();
for (k, field) in &store.model.fields { for (k, field) in &store.model.fields {
@ -98,9 +118,7 @@ fn collect_card_form(store: &Store, mut form: MapFromForm) -> IndexMap::<String,
FieldKind::Text | FieldKind::String | FieldKind::FreeEnum { .. } => { FieldKind::Text | FieldKind::String | FieldKind::FreeEnum { .. } => {
Value::String(input) Value::String(input)
} }
FieldKind::Bool { .. } => { FieldKind::Bool { .. } => serde_json::to_value(true).unwrap(),
serde_json::to_value(true).unwrap()
}
FieldKind::Int { min, max, default } => { FieldKind::Int { min, max, default } => {
let mut val: i64 = if input.is_empty() { let mut val: i64 = if input.is_empty() {
*default *default
@ -139,20 +157,19 @@ fn collect_card_form(store: &Store, mut form: MapFromForm) -> IndexMap::<String,
if options.contains(&input) { if options.contains(&input) {
Value::String(input) Value::String(input)
} else { } else {
let val = default let val = default.as_ref().map(ToOwned::to_owned).unwrap_or_else(|| {
.as_ref() options
.map(ToOwned::to_owned)
.unwrap_or_else(|| options
.first() .first()
.expect("fixed enum must have values") .expect("fixed enum must have values")
.to_owned() .to_owned()
); });
Value::String(val) Value::String(val)
} }
} }
FieldKind::Tags { options } => { FieldKind::Tags { options } => {
let tags: Vec<String> = input.split(' ') let tags: Vec<String> = input
.split(' ')
.map(ToOwned::to_owned) .map(ToOwned::to_owned)
.filter_map(|tag| { .filter_map(|tag| {
if options.contains(&tag) { if options.contains(&tag) {
@ -160,12 +177,14 @@ fn collect_card_form(store: &Store, mut form: MapFromForm) -> IndexMap::<String,
} else { } else {
None None
} }
}).collect(); })
.collect();
serde_json::to_value(tags).unwrap() serde_json::to_value(tags).unwrap()
} }
FieldKind::FreeTags { .. } => { FieldKind::FreeTags { .. } => {
let tags: Vec<String> = input.split(' ') let tags: Vec<String> = input
.split(' ')
.map(str::trim) .map(str::trim)
.filter(|s| !s.is_empty()) .filter(|s| !s.is_empty())
.map(ToOwned::to_owned) .map(ToOwned::to_owned)
@ -204,7 +223,7 @@ fn route_add(store: State<RwLock<Store>>) -> Template {
let rg = store.read(); let rg = store.read();
let context = AddCardContext { let context = AddCardContext {
fields: render_empty_fields(&rg) fields: render_empty_fields(&rg),
}; };
Template::render("add", context) Template::render("add", context)
@ -237,7 +256,11 @@ fn route_edit(id: usize, store: State<RwLock<Store>>) -> Option<Template> {
} }
#[post("/edit/<id>", data = "<form>")] #[post("/edit/<id>", data = "<form>")]
fn route_edit_save(id: usize, form: Form<MapFromForm>, store: State<RwLock<Store>>) -> Option<Redirect> { fn route_edit_save(
id: usize,
form: Form<MapFromForm>,
store: State<RwLock<Store>>,
) -> Option<Redirect> {
let mut rg = store.write(); let mut rg = store.write();
let card = collect_card_form(&rg, form.into_inner()); let card = collect_card_form(&rg, form.into_inner());
@ -250,9 +273,11 @@ fn route_edit_save(id: usize, form: Form<MapFromForm>, store: State<RwLock<Store
fn route_delete(id: usize, store: State<RwLock<Store>>) -> Redirect { fn route_delete(id: usize, store: State<RwLock<Store>>) -> Redirect {
let mut rg = store.write(); let mut rg = store.write();
let page = find_page_with_card(&rg, id).unwrap_or(0);
rg.delete_card(id); rg.delete_card(id);
Redirect::found(uri!(route_index: page=_, card=_)) Redirect::found(uri!(route_index: page=page, card=_))
} }
fn main() { fn main() {
@ -264,12 +289,16 @@ fn main() {
.attach(Template::fairing()) .attach(Template::fairing())
.manage(RwLock::new(store)) .manage(RwLock::new(store))
.mount("/", StaticFiles::from(cwd.join("templates/static/"))) .mount("/", StaticFiles::from(cwd.join("templates/static/")))
.mount("/", routes![ .mount(
route_index, "/",
route_add, routes![
route_add_save, route_index,
route_edit, route_add,
route_edit_save, route_add_save,
route_delete, route_edit,
]).launch(); route_edit_save,
route_delete,
],
)
.launch();
} }

@ -1,7 +1,7 @@
use crate::store::model::FieldKind; use crate::store::model::FieldKind;
use crate::store::{model, Indexes, Store};
use serde_json::Value; use serde_json::Value;
use std::borrow::Cow; use std::borrow::Cow;
use crate::store::{model, Indexes, Store};
use lazy_static::lazy_static; use lazy_static::lazy_static;
@ -30,7 +30,7 @@ impl<'a> RenderedField<'a> {
key: &'i String, key: &'i String,
field: &'i model::Field, field: &'i model::Field,
value: Option<&'i Value>, value: Option<&'i Value>,
index: &'i Indexes index: &'i Indexes,
) -> RenderedField<'i> { ) -> RenderedField<'i> {
let mut rendered = RenderedField::default(); let mut rendered = RenderedField::default();
rendered.key = key.as_str().into(); rendered.key = key.as_str().into();
@ -134,21 +134,23 @@ impl<'a> RenderedField<'a> {
if let Some(Value::String(s)) = value { if let Some(Value::String(s)) = value {
rendered.value = Cow::Borrowed(&s.as_str()); rendered.value = Cow::Borrowed(&s.as_str());
} }
}, }
FieldKind::Enum { .. } | FieldKind::FreeEnum { .. } => { FieldKind::Enum { .. } | FieldKind::FreeEnum { .. } => {
if let Some(Value::String(s)) = value { if let Some(Value::String(s)) = value {
rendered.value = Cow::Borrowed(&s.as_str()); rendered.value = Cow::Borrowed(&s.as_str());
} }
}, }
FieldKind::Tags { .. } | FieldKind::FreeTags { .. } => { FieldKind::Tags { .. } | FieldKind::FreeTags { .. } => {
if let Some(v) = value { if let Some(v) = value {
rendered.value = serde_json::from_value::<Vec<String>>(v.clone()) rendered.value = serde_json::from_value::<Vec<String>>(v.clone())
.unwrap().join(" ").into(); .unwrap()
.join(" ")
.into();
rendered.tags_json = serde_json::to_string(v).unwrap(); rendered.tags_json = serde_json::to_string(v).unwrap();
} else { } else {
rendered.tags_json = "[]".into(); rendered.tags_json = "[]".into();
} }
}, }
_ => {} _ => {}
} }
@ -156,24 +158,35 @@ impl<'a> RenderedField<'a> {
} }
} }
#[derive(Serialize,Debug)] #[derive(Serialize, Debug)]
pub struct RenderedCard<'a> { pub struct RenderedCard<'a> {
pub fields : Vec<RenderedField<'a>>, pub fields: Vec<RenderedField<'a>>,
pub id : usize, pub id: usize,
} }
pub fn render_empty_fields(store : &Store) -> Vec<RenderedField> { pub fn render_empty_fields(store: &Store) -> Vec<RenderedField> {
let indexes = &store.index; let indexes = &store.index;
store.model.fields.iter().map(|(key, field)| { store
RenderedField::from_template_field(key, field, None, indexes) .model
}).collect() .fields
.iter()
.map(|(key, field)| RenderedField::from_template_field(key, field, None, indexes))
.collect()
} }
pub fn render_card_fields<'a>(store : &'a Store, values : &'a serde_json::Map<String, Value>) -> Vec<RenderedField<'a>> { pub fn render_card_fields<'a>(
store: &'a Store,
values: &'a serde_json::Map<String, Value>,
) -> Vec<RenderedField<'a>> {
let indexes = &store.index; let indexes = &store.index;
store.model.fields.iter().map(|(key, field)| { store
RenderedField::from_template_field(key, field, values.get(key), indexes) .model
}).collect() .fields
.iter()
.map(|(key, field)| {
RenderedField::from_template_field(key, field, values.get(key), indexes)
})
.collect()
} }

@ -1,56 +1,55 @@
use std::fs::{File, OpenOptions};
use std::io::{Read, Write, Error};
use std::path::{Path, PathBuf};
use rocket::request::FromForm;
use crate::store::model::Model; use crate::store::model::Model;
use std::collections::HashMap; use indexmap::map::IndexMap;
use serde::Serialize; use serde::Serialize;
use serde_json::Value; use serde_json::Value;
use indexmap::map::IndexMap; use std::collections::HashMap;
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
pub mod model;
pub mod form; pub mod form;
pub mod model;
/// Store instance /// Store instance
#[derive(Debug)] #[derive(Debug)]
pub struct Store { pub struct Store {
path : PathBuf, path: PathBuf,
pub model: Model, pub model: Model,
pub data: Cards, pub data: Cards,
pub index : Indexes, pub index: Indexes,
} }
/// Indexes loaded from the indexes file /// Indexes loaded from the indexes file
#[derive(Serialize,Deserialize,Debug,Default)] #[derive(Serialize, Deserialize, Debug, Default)]
pub struct Indexes { pub struct Indexes {
pub free_enums : HashMap<String, Vec<String>>, pub free_enums: HashMap<String, Vec<String>>,
pub free_tags : HashMap<String, Vec<String>>, pub free_tags: HashMap<String, Vec<String>>,
} }
/// Struct loaded from the repositroy config file /// Struct loaded from the repositroy config file
#[derive(Deserialize,Debug)] #[derive(Deserialize, Debug)]
struct RepositoryConfig { struct RepositoryConfig {
pub model : Model, pub model: Model,
} }
#[derive(Serialize,Deserialize,Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct Cards { pub struct Cards {
#[serde(default)] #[serde(default)]
pub cards : IndexMap<usize, Value>, pub cards: IndexMap<usize, Value>,
#[serde(default)] #[serde(default)]
pub counter: usize, pub counter: usize,
} }
const REPO_CONFIG_FILE : &'static str = "repository.yaml"; const REPO_CONFIG_FILE: &'static str = "repository.yaml";
const REPO_DATA_FILE : &'static str = "data.json"; const REPO_DATA_FILE: &'static str = "data.json";
const REPO_INDEX_FILE : &'static str = "index.json"; const REPO_INDEX_FILE: &'static str = "index.json";
impl Store { impl Store {
pub fn new(path: impl AsRef<Path>) -> Self { pub fn new(path: impl AsRef<Path>) -> Self {
let file = load_file(path.as_ref().join(REPO_CONFIG_FILE)); let file = load_file(path.as_ref().join(REPO_CONFIG_FILE));
let repository_config : RepositoryConfig = serde_yaml::from_str(&file) let repository_config: RepositoryConfig =
.expect("Error parsing repository config file."); serde_yaml::from_str(&file).expect("Error parsing repository config file.");
let items = load_file_or(path.as_ref().join(REPO_DATA_FILE), "{}"); let items = load_file_or(path.as_ref().join(REPO_DATA_FILE), "{}");
let indexes = load_file_or(path.as_ref().join(REPO_INDEX_FILE), "{}"); let indexes = load_file_or(path.as_ref().join(REPO_INDEX_FILE), "{}");
@ -72,10 +71,11 @@ impl Store {
.expect("Error opening data file for writing."); .expect("Error opening data file for writing.");
let serialized = serde_json::to_string_pretty(&self.data).expect("Error serialize."); let serialized = serde_json::to_string_pretty(&self.data).expect("Error serialize.");
file.write(serialized.as_bytes()).expect("Error write data file"); file.write(serialized.as_bytes())
.expect("Error write data file");
} }
pub fn add_card(&mut self, values : IndexMap::<String, Value>) -> usize { pub fn add_card(&mut self, values: IndexMap<String, Value>) -> usize {
let packed = serde_json::to_value(values).expect("Error serialize"); let packed = serde_json::to_value(values).expect("Error serialize");
if let p @ Value::Object(_) = packed { if let p @ Value::Object(_) = packed {
let id = self.data.counter; let id = self.data.counter;
@ -88,7 +88,7 @@ impl Store {
} }
} }
pub fn update_card(&mut self, id : usize, values : IndexMap::<String, Value>) { pub fn update_card(&mut self, id: usize, values: IndexMap<String, Value>) {
let packed = serde_json::to_value(values).expect("Error serialize"); let packed = serde_json::to_value(values).expect("Error serialize");
if !self.data.cards.contains_key(&id) { if !self.data.cards.contains_key(&id) {
@ -104,7 +104,7 @@ impl Store {
self.persist() self.persist()
} }
pub fn delete_card(&mut self, id : usize) { pub fn delete_card(&mut self, id: usize) {
self.data.cards.remove(&id); self.data.cards.remove(&id);
self.persist() self.persist()
@ -112,17 +112,19 @@ impl Store {
} }
fn load_file(path: impl AsRef<Path>) -> String { fn load_file(path: impl AsRef<Path>) -> String {
let mut file= File::open(&path).expect(&format!("Error opening file {}", path.as_ref().display())); let mut file =
File::open(&path).expect(&format!("Error opening file {}", path.as_ref().display()));
let mut buf = String::new(); let mut buf = String::new();
file.read_to_string(&mut buf).expect(&format!("Error reading file {}", path.as_ref().display())); file.read_to_string(&mut buf)
.expect(&format!("Error reading file {}", path.as_ref().display()));
buf buf
} }
fn load_file_or(file : impl AsRef<Path>, def : impl Into<String>) -> String { fn load_file_or(file: impl AsRef<Path>, def: impl Into<String>) -> String {
let mut file = match File::open(file) { let mut file = match File::open(file) {
Ok(file) => file, Ok(file) => file,
Err(_) => return def.into() Err(_) => return def.into(),
}; };
let mut buf = String::new(); let mut buf = String::new();

@ -1,4 +1,3 @@
use std::collections::HashMap;
use indexmap::map::IndexMap; use indexmap::map::IndexMap;
/// A data card's model. /// A data card's model.

Loading…
Cancel
Save