#[macro_use] extern crate rocket; use rocket::fs::FileServer; use rocket::fs::Options; use rocket::http::ContentType; use rocket::request::Request; use rocket::response::Redirect; use rocket::response::{self, Responder, Response}; use rocket::State; use rocket_dyn_templates::tera; use rocket_dyn_templates::Template; use rand::distributions::Uniform; use rand::Rng; use lazy_static::lazy_static; use std::collections::HashMap; use std::ops::Deref; use std::sync::Arc; use chgk_ledb_lib::async_db; use chgk_ledb_lib::questions::Question; use mini_moka::sync::Cache; use std::time::Duration; const DB_FILENAME: &str = "db.dat"; lazy_static! { static ref EMPTY_MAP: HashMap = HashMap::new(); } trait ErrorEmpty { type Output; fn err_empty(self) -> Result; } impl ErrorEmpty for Result { type Output = T; fn err_empty(self) -> Result { self.map_err(|_| ()) } } #[derive(Debug, Responder)] enum WebError { #[response(status = 404)] NotFound(Template), #[response(status = 500)] ServerError(Template), } impl WebError { fn not_found() -> Self { WebError::NotFound(Template::render("404", EMPTY_MAP.deref())) } fn server_error() -> Self { WebError::ServerError(Template::render("500", EMPTY_MAP.deref())) } } /// wrapper for terra:Value (json, context), to cache values by ref /// implements Responder as json content type #[derive(Clone)] struct ArcTemplateData { value: Arc, } impl Deref for ArcTemplateData { type Target = tera::Value; fn deref(&self) -> &Self::Target { self.value.deref() } } impl ArcTemplateData { fn new(value: tera::Value) -> ArcTemplateData { ArcTemplateData { value: Arc::new(value), } } fn render(&self, name: &'static str) -> Template { Template::render(name, self.deref()) } } impl<'r> Responder<'r, 'static> for ArcTemplateData { fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> { Response::build_from(self.to_string().respond_to(req)?) .header(ContentType::JSON) .ok() } } type TemplateCache = mini_moka::sync::Cache; type DataBaseInner = async_db::Reader; type DataBase = Arc; struct AppState { db: DataBase, database_distribution: Uniform, } impl From for AppState { fn from(db: DataBaseInner) -> Self { let last_id = db.len(); let database_distribution = rand::distributions::Uniform::new_inclusive(1usize, last_id); let db = Arc::new(db); Self { db, database_distribution, } } } fn random_question_id(database_distribution: &Uniform) -> usize { let mut rng = rand::thread_rng(); rng.sample(database_distribution) } async fn get_question(db: &DataBase, id: usize) -> Result { db.get(id - 1).await.err_empty() } async fn get_question_data( data: &AppState, cache: &TemplateCache, id: usize, ) -> Option { if let Some(value) = cache.get(&id) { return Some(value); } match get_question(&data.db, id).await { Ok(question) => { let mut context = tera::to_value(question).expect("question serialize"); if context.is_object() { let next_id = random_question_id(&data.database_distribution); context["next"] = tera::to_value(next_id).expect("question id serialize"); } let data = ArcTemplateData::new(context); cache.insert(id, data.clone()); Some(data) } Err(_) => None, } } async fn show_question_details( template_name: &'static str, data: &AppState, cache: &TemplateCache, id: usize, ) -> Option