You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
192 lines
4.6 KiB
192 lines
4.6 KiB
#![feature(proc_macro_hygiene, decl_macro)]
|
|
|
|
#[macro_use]
|
|
extern crate rocket;
|
|
#[macro_use]
|
|
extern crate serde;
|
|
|
|
use serde_json::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, MapFromForm, collect_card_form};
|
|
use crate::store::Store;
|
|
use parking_lot::RwLock;
|
|
|
|
use rocket::request::Form;
|
|
use rocket::response::Redirect;
|
|
use rocket::State;
|
|
use std::env;
|
|
|
|
#[derive(Serialize, Debug)]
|
|
pub struct ListContext<'a> {
|
|
pub fields: Vec<RenderedField<'a>>,
|
|
pub cards: Vec<RenderedCard<'a>>,
|
|
pub page: usize,
|
|
pub pages: usize,
|
|
}
|
|
|
|
const PER_PAGE: usize = 20; // TODO configurable
|
|
|
|
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>")]
|
|
fn route_index(store: State<RwLock<Store>>, page: Option<usize>) -> 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 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(Serialize)]
|
|
struct AddCardContext<'a> {
|
|
pub fields: Vec<RenderedField<'a>>,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
struct EditCardContext<'a> {
|
|
pub fields: Vec<RenderedField<'a>>,
|
|
pub id: usize,
|
|
}
|
|
|
|
#[get("/add")]
|
|
fn route_add(store: State<RwLock<Store>>) -> Template {
|
|
let rg = store.read();
|
|
|
|
let context = AddCardContext {
|
|
fields: render_empty_fields(&rg),
|
|
};
|
|
|
|
Template::render("add", context)
|
|
}
|
|
|
|
#[post("/add", data = "<form>")]
|
|
fn route_add_save(form: Form<MapFromForm>, store: State<RwLock<Store>>) -> Redirect {
|
|
let mut wg = store.write();
|
|
|
|
let card = collect_card_form(&wg, form.into_inner());
|
|
let id = wg.add_card(card);
|
|
|
|
let page = find_page_with_card(&wg, id).unwrap_or(0);
|
|
Redirect::found(uri!(route_index: page))
|
|
}
|
|
|
|
#[get("/edit/<id>")]
|
|
fn route_edit(id: usize, store: State<RwLock<Store>>) -> Option<Template> {
|
|
let rg = store.read();
|
|
|
|
if let Some(Value::Object(ref map)) = rg.data.cards.get(&id) {
|
|
let context = EditCardContext {
|
|
fields: render_card_fields(&rg, map),
|
|
id,
|
|
};
|
|
|
|
Some(Template::render("edit", context))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
#[post("/edit/<id>", data = "<form>")]
|
|
fn route_edit_save(
|
|
id: usize,
|
|
form: Form<MapFromForm>,
|
|
store: State<RwLock<Store>>,
|
|
) -> Option<Redirect> {
|
|
let mut wg = store.write();
|
|
|
|
let card = collect_card_form(&wg, form.into_inner());
|
|
wg.update_card(id, card);
|
|
|
|
let page = find_page_with_card(&wg, id).unwrap_or(0);
|
|
Some(Redirect::found(uri!(route_index: page)))
|
|
}
|
|
|
|
#[get("/delete/<id>")]
|
|
fn route_delete(id: usize, store: State<RwLock<Store>>) -> Redirect {
|
|
let mut wg = store.write();
|
|
|
|
// must find page before deleting
|
|
let page = find_page_with_card(&wg, id).unwrap_or(0);
|
|
wg.delete_card(id);
|
|
|
|
Redirect::found(uri!(route_index: page))
|
|
}
|
|
|
|
#[get("/maintenance/reindex")]
|
|
fn route_reindex(store: State<RwLock<Store>>) -> Redirect {
|
|
let mut wg = store.write();
|
|
wg.rebuild_indexes();
|
|
wg.persist();
|
|
Redirect::found(uri!(route_index: _))
|
|
}
|
|
|
|
fn main() {
|
|
let cwd = env::current_dir().unwrap();
|
|
let data_dir = cwd.join("data");
|
|
let store = Store::new(data_dir);
|
|
|
|
rocket::ignite()
|
|
.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,
|
|
route_reindex,
|
|
],
|
|
)
|
|
.launch();
|
|
}
|
|
|