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

@ -1,7 +1,7 @@
use crate::store::model::FieldKind;
use crate::store::{model, Indexes, Store};
use serde_json::Value;
use std::borrow::Cow;
use crate::store::{model, Indexes, Store};
use lazy_static::lazy_static;
@ -30,7 +30,7 @@ impl<'a> RenderedField<'a> {
key: &'i String,
field: &'i model::Field,
value: Option<&'i Value>,
index: &'i Indexes
index: &'i Indexes,
) -> RenderedField<'i> {
let mut rendered = RenderedField::default();
rendered.key = key.as_str().into();
@ -134,21 +134,23 @@ impl<'a> RenderedField<'a> {
if let Some(Value::String(s)) = value {
rendered.value = Cow::Borrowed(&s.as_str());
}
},
}
FieldKind::Enum { .. } | FieldKind::FreeEnum { .. } => {
if let Some(Value::String(s)) = value {
rendered.value = Cow::Borrowed(&s.as_str());
}
},
}
FieldKind::Tags { .. } | FieldKind::FreeTags { .. } => {
if let Some(v) = value {
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();
} else {
rendered.tags_json = "[]".into();
}
},
}
_ => {}
}
@ -156,24 +158,35 @@ impl<'a> RenderedField<'a> {
}
}
#[derive(Serialize,Debug)]
#[derive(Serialize, Debug)]
pub struct RenderedCard<'a> {
pub fields : Vec<RenderedField<'a>>,
pub id : usize,
pub fields: Vec<RenderedField<'a>>,
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;
store.model.fields.iter().map(|(key, field)| {
RenderedField::from_template_field(key, field, None, indexes)
}).collect()
store
.model
.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;
store.model.fields.iter().map(|(key, field)| {
RenderedField::from_template_field(key, field, values.get(key), indexes)
}).collect()
store
.model
.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 std::collections::HashMap;
use indexmap::map::IndexMap;
use serde::Serialize;
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 model;
/// Store instance
#[derive(Debug)]
pub struct Store {
path : PathBuf,
path: PathBuf,
pub model: Model,
pub data: Cards,
pub index : Indexes,
pub index: Indexes,
}
/// Indexes loaded from the indexes file
#[derive(Serialize,Deserialize,Debug,Default)]
#[derive(Serialize, Deserialize, Debug, Default)]
pub struct Indexes {
pub free_enums : HashMap<String, Vec<String>>,
pub free_tags : HashMap<String, Vec<String>>,
pub free_enums: HashMap<String, Vec<String>>,
pub free_tags: HashMap<String, Vec<String>>,
}
/// Struct loaded from the repositroy config file
#[derive(Deserialize,Debug)]
#[derive(Deserialize, Debug)]
struct RepositoryConfig {
pub model : Model,
pub model: Model,
}
#[derive(Serialize,Deserialize,Debug)]
#[derive(Serialize, Deserialize, Debug)]
pub struct Cards {
#[serde(default)]
pub cards : IndexMap<usize, Value>,
pub cards: IndexMap<usize, Value>,
#[serde(default)]
pub counter: usize,
}
const REPO_CONFIG_FILE : &'static str = "repository.yaml";
const REPO_DATA_FILE : &'static str = "data.json";
const REPO_INDEX_FILE : &'static str = "index.json";
const REPO_CONFIG_FILE: &'static str = "repository.yaml";
const REPO_DATA_FILE: &'static str = "data.json";
const REPO_INDEX_FILE: &'static str = "index.json";
impl Store {
pub fn new(path: impl AsRef<Path>) -> Self {
let file = load_file(path.as_ref().join(REPO_CONFIG_FILE));
let repository_config : RepositoryConfig = serde_yaml::from_str(&file)
.expect("Error parsing repository config file.");
let repository_config: RepositoryConfig =
serde_yaml::from_str(&file).expect("Error parsing repository config 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), "{}");
@ -72,10 +71,11 @@ impl Store {
.expect("Error opening data file for writing.");
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");
if let p @ Value::Object(_) = packed {
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");
if !self.data.cards.contains_key(&id) {
@ -104,7 +104,7 @@ impl Store {
self.persist()
}
pub fn delete_card(&mut self, id : usize) {
pub fn delete_card(&mut self, id: usize) {
self.data.cards.remove(&id);
self.persist()
@ -112,17 +112,19 @@ impl Store {
}
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();
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
}
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) {
Ok(file) => file,
Err(_) => return def.into()
Err(_) => return def.into(),
};
let mut buf = String::new();

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

Loading…
Cancel
Save