use super::{cli, config, db, static_files, templates}; use handlebars::to_json; use serde::{Deserialize, Serialize}; use serde_json::value::Value as Json; use sha2::{Digest, Sha512Trunc256}; use std::{ convert::{TryFrom, TryInto}, sync::{Arc, RwLock}, }; use warp::Filter; type PasswordHash = [u8; 32]; #[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize)] struct AdId([u8; 16]); impl AdId { fn random() -> Self { Self(rand::random()) } } impl AsRef<[u8]> for AdId { fn as_ref(&self) -> &[u8] { &self.0 } } impl TryFrom<&[u8]> for AdId { type Error = std::array::TryFromSliceError; fn try_from(v: &[u8]) -> Result { Ok(Self(v.try_into()?)) } } pub async fn start_server( config: config::Config, dbs: db::Dbs, templates: templates::Templates<'static>, opt: cli::MainOpt, ) { let templates = Arc::new(templates); let cache_ads = Arc::new(RwLock::new(to_json( dbs.ads .iter() .filter_map(|x| { let (ad_id, ad) = x.ok()?; Some(AdWithId { id: hex::encode(ad_id.as_ref()), ad: bincode::deserialize::(&ad).ok()?, }) }) .collect::>(), ))); let dbs = Arc::new(dbs); let handle_index = { let cache_ads = cache_ads.clone(); move |errors: &[ErrorTemplate]| { warp::reply::html( templates .hb .render( "index.html", &IndexTemplate { lang: "fr", errors, ads: &*cache_ads.read().unwrap(), }, ) .unwrap_or_else(|e| e.to_string()), ) } }; let route_static = warp::path("static") .and(warp::get()) .and(warp::fs::dir(opt.dir.0.join(static_files::STATIC_DIR))); let handle_new_ad = { let handle_index = handle_index.clone(); let dbs = dbs.clone(); let cache_ads = cache_ads.clone(); move |query: NewAdQuery| { let mut hasher = Sha512Trunc256::new(); hasher.update(query.psw); dbs.ads .insert( AdId::random(), bincode::serialize(&Ad { author: query.author, password: hasher.finalize()[..].try_into().unwrap(), price: query.price, pubkey: (!query.pubkey.is_empty()).then_some(query.pubkey), quantity: query.quantity, time: 0, title: query.title, }) .unwrap(), ) .unwrap(); dbs.ads.flush().unwrap(); let mut cache_ads = cache_ads.write().unwrap(); *cache_ads = to_json( dbs.ads .iter() .filter_map(|x| { let (ad_id, ad) = x.ok()?; Some(AdWithId { id: hex::encode(ad_id.as_ref()), ad: bincode::deserialize::(&ad).ok()?, }) }) .collect::>(), ); drop(cache_ads); handle_index(&[]) } }; let handle_rm_ad = { let handle_index = handle_index.clone(); move |query: RmAdQuery| { let mut hasher = Sha512Trunc256::new(); hasher.update(query.psw); let password: PasswordHash = hasher.finalize()[..].try_into().unwrap(); /*query .ads .into_iter() .filter_map(|x| Some(AdId::try_from(hex::decode(x).ok()?.as_ref()).ok()?)) .for_each(|ad_id| { if let Some(raw) = dbs.ads.get(&ad_id).unwrap() { if let Ok(ad) = bincode::deserialize::(&raw) { if ad.password == password { dbs.ads.remove(&ad_id).unwrap(); } } } });*/ if let Ok(ad_id) = hex::decode(query.ad) { if let Ok(ad_id) = AdId::try_from(ad_id.as_ref()) { if let Some(raw) = dbs.ads.get(&ad_id).unwrap() { if let Ok(ad) = bincode::deserialize::(&raw) { if ad.password == password { dbs.ads.remove(&ad_id).unwrap(); dbs.ads.flush().unwrap(); let mut cache_ads = cache_ads.write().unwrap(); *cache_ads = to_json( dbs.ads .iter() .filter_map(|x| { let (ad_id, ad) = x.ok()?; Some(AdWithId { id: hex::encode(ad_id.as_ref()), ad: bincode::deserialize::(&ad).ok()?, }) }) .collect::>(), ); } else { return handle_index(&[ErrorTemplate { text: "Le mot de passe de l'annonce est incorrect.", }]); } } } } } handle_index(&[]) } }; let route_index = warp::path::end().and(warp::get().map(move || handle_index(&[])).or( warp::post().and( warp::body::form::().map(move |query: Query| match query { Query::NewAdQuery(query) => handle_new_ad(query), Query::RmAdQuery(query) => handle_rm_ad(query), }), ), )); warp::serve(route_static.or(route_index)) .run(config.listen) .await; } #[derive(Serialize)] struct IndexTemplate<'a> { lang: &'a str, errors: &'a [ErrorTemplate<'a>], ads: &'a Json, } #[derive(Serialize)] struct ErrorTemplate<'a> { text: &'a str, } #[derive(Clone, Debug, Deserialize)] struct NewAdQuery { author: String, price: String, psw: String, pubkey: String, quantity: String, title: String, } #[derive(Clone, Debug, Deserialize)] struct RmAdQuery { ad: String, psw: String, } #[derive(Clone, Debug, Deserialize)] #[serde(tag = "a")] enum Query { #[serde(rename = "new_ad")] NewAdQuery(NewAdQuery), #[serde(rename = "rm_ad")] RmAdQuery(RmAdQuery), } #[derive(Clone, Debug, Deserialize, Serialize)] struct Ad { author: String, password: PasswordHash, price: String, pubkey: Option, quantity: String, time: u64, title: String, } #[derive(Clone, Debug, Serialize)] struct AdWithId { id: String, ad: Ad, }