#![feature(proc_macro_hygiene, decl_macro)] #[macro_use] extern crate rocket; #[macro_use] extern crate serde; use serde_json::{json, Number, Value}; //use rocket::request::FromSegments; //use rocket::http::uri::Segments; use rocket_contrib::serve::StaticFiles; 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 indexmap::map::IndexMap; use parking_lot::RwLock; use rocket::http::Status; use rocket::request::{Form, FormItems, FromForm, LenientForm}; use rocket::response::Redirect; use rocket::{Data, State}; use std::env; use std::ops::Deref; #[derive(Serialize, Debug)] pub struct ListContext<'a> { pub fields: Vec>, pub cards: Vec>, pub page: usize, pub pages: usize, } const per_page: usize = 20; fn find_page_with_card(store: &Store, card_id: usize) -> Option { if let Some((n, _)) = store .data .cards .iter() .enumerate() .find(|(_n, (id, _card))| **id == card_id) { Some(n / per_page) } else { None } } #[get("/?&")] fn route_index(store: State>, page: Option, card: Option) -> Template { let rg = store.read(); 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 { 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() .skip(page * per_page) .take(per_page) .filter_map(|(id, card)| { if let Value::Object(ref map) = card { Some(RenderedCard { fields: render_card_fields(&rg, map), id: *id, }) } else { None } }) .collect(), }; Template::render("index", context) } #[derive(Default)] struct MapFromForm { pub data: IndexMap, } impl<'a> FromForm<'a> for MapFromForm { type Error = (); fn from_form(items: &mut FormItems, _strict: bool) -> Result { let mut new = MapFromForm::default(); items.for_each(|item| { let (k, v) = item.key_value_decoded(); new.data.insert(k, v); }); Ok(new) } } fn collect_card_form(store: &Store, mut form: MapFromForm) -> IndexMap { let mut card = IndexMap::new(); for (k, field) in &store.model.fields { let mut value: Option = None; if let Some(input) = form.data.remove(k) { value = Some(match &field.kind { FieldKind::Text | FieldKind::String | FieldKind::FreeEnum { .. } => { Value::String(input) } FieldKind::Bool { .. } => serde_json::to_value(true).unwrap(), FieldKind::Int { min, max, default } => { let mut val: i64 = if input.is_empty() { *default } else { input.parse().expect("Error parse number") }; if let Some(min) = min { val = val.max(*min); } if let Some(max) = max { val = val.min(*max); } serde_json::to_value(val).unwrap() } FieldKind::Float { min, max, default } => { let mut val: f64 = if input.is_empty() { *default } else { input.parse().expect("Error parse number") }; if let Some(min) = min { val = val.min(*min); } if let Some(max) = max { val = val.max(*max); } serde_json::to_value(val).unwrap() } FieldKind::Enum { options, default } => { if options.contains(&input) { Value::String(input) } else { 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 = input .split(' ') .map(ToOwned::to_owned) .filter_map(|tag| { if options.contains(&tag) { Some(tag) } else { None } }) .collect(); serde_json::to_value(tags).unwrap() } FieldKind::FreeTags { .. } => { let tags: Vec = input .split(' ') .map(str::trim) .filter(|s| !s.is_empty()) .map(ToOwned::to_owned) .collect(); serde_json::to_value(tags).unwrap() } }); } else { if let FieldKind::Bool { .. } = field.kind { value = Some(Value::Bool(false)); } } if let Some(v) = value { card.insert(k.to_owned(), v); } } card } #[derive(Serialize)] struct AddCardContext<'a> { pub fields: Vec>, } #[derive(Serialize)] struct EditCardContext<'a> { pub fields: Vec>, pub id: usize, } #[get("/add")] fn route_add(store: State>) -> Template { let rg = store.read(); let context = AddCardContext { fields: render_empty_fields(&rg), }; Template::render("add", context) } #[post("/add", data = "
")] fn route_add_save(form: Form, store: State>) -> Redirect { let mut rg = store.write(); let card = collect_card_form(&rg, form.into_inner()); let id = rg.add_card(card); Redirect::found(uri!(route_index: page=_, card=id)) } #[get("/edit/")] fn route_edit(id: usize, store: State>) -> Option