#![feature(proc_macro_hygiene, decl_macro)] extern crate ledb; extern crate serde; extern crate serde_json; #[macro_use] extern crate rocket; extern crate rocket_contrib; use rocket::response::Redirect; use rocket::{Rocket, State}; use rocket_contrib::serve::StaticFiles; use rocket_contrib::templates::Template; use rand::distributions::Uniform; use rand::Rng; use ledb::{Options, Storage}; use chgk_ledb_lib::questions::Question; trait ErrorEmpty { type Output; fn err_empty(self) -> Result; } impl ErrorEmpty for Result { type Output = T; fn err_empty(self) -> Result { self.map_err(|_| ()) } } struct AppState { storage: Storage, database_distribution: Uniform, } fn get_database_distribution(storage: &Storage) -> Uniform { let collection = storage .collection("questions") .expect("collection \"questions\""); let last_id = collection.last_id().expect("\"questions\" last_id"); rand::distributions::Uniform::new_inclusive(1u32, last_id) } fn random_question_id(database_distribution: &Uniform) -> u32 { let mut rng = rand::thread_rng(); rng.sample(database_distribution) } fn get_question(storage: &Storage, id: u32) -> Result { storage .collection("questions") .err_empty()? .get::(id) .err_empty()? .ok_or(()) } fn show_question_details(template_name: &'static str, data: &AppState, id: u32) -> Template { match get_question(&data.storage, id) { Ok(question) => { let mut context = serde_json::to_value(question).expect("question serialize"); if context.is_object() { let next_id = random_question_id(&data.database_distribution); context["next"] = serde_json::to_value(next_id).expect("question id serialize"); } Template::render(template_name, &context) } Err(_) => { use std::collections::HashMap; let context: HashMap = HashMap::new(); Template::render("404", &context) } } } #[get("/q/")] fn show_question(data: State, id: u32) -> Template { show_question_details("question", data.inner(), id) } #[get("/q//a")] fn show_answer(data: State, id: u32) -> Template { show_question_details("answer", data.inner(), id) } #[get("/q/0")] fn question0() -> Redirect { Redirect::to("/") } #[get("/q/0/a")] fn answer0() -> Redirect { Redirect::to("/") } #[get("/")] fn index(data: State) -> Redirect { let id = random_question_id(&data.database_distribution); Redirect::temporary(format!("/q/{}", id)) } #[catch(404)] fn not_found(_req: &rocket::Request) -> Template { use std::collections::HashMap; let context: HashMap = HashMap::new(); Template::render("404", &context) } fn rocket() -> Rocket { let options: Options = serde_json::from_value(serde_json::json!({ "read_only": true, "no_lock": true, })) .unwrap(); let mut state = AppState { storage: Storage::new("db", options).expect("open db"), database_distribution: Uniform::new(1, 3), }; state.database_distribution = get_database_distribution(&state.storage); let state = state; rocket::ignite() .manage(state) .register(catchers![not_found]) .mount( "/", routes![index, show_question, show_answer, question0, answer0], ) .mount("/q", routes![index]) .mount("/q/static", StaticFiles::from("static/")) .attach(Template::fairing()) } fn main() { rocket().launch(); }