From bcfccd6a0e68689cfb28181920ef3de7b65556b5 Mon Sep 17 00:00:00 2001 From: tuxmain Date: Mon, 2 Nov 2020 10:57:50 +0100 Subject: [PATCH] Ads db, new/rm, better query handling, icon --- Cargo.lock | 19 +++++ Cargo.toml | 4 +- src/db.rs | 12 ++- src/server.rs | 194 +++++++++++++++++++++++++++++++++++-------- src/static_files.rs | 1 + static/icon.png | Bin 0 -> 9555 bytes templates/index.html | 55 +++++++----- 7 files changed, 227 insertions(+), 58 deletions(-) create mode 100644 static/icon.png diff --git a/Cargo.lock b/Cargo.lock index f128aa0..77bebf5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -426,6 +426,8 @@ dependencies = [ "bincode", "dirs", "handlebars", + "hex", + "rand 0.7.3", "serde", "serde_json", "sha2", @@ -518,6 +520,12 @@ dependencies = [ "libc", ] +[[package]] +name = "hex" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35" + [[package]] name = "http" version = "0.2.1" @@ -778,6 +786,16 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "once_cell" version = "1.4.1" @@ -1416,6 +1434,7 @@ dependencies = [ "lazy_static", "memchr", "mio", + "num_cpus", "pin-project-lite", "slab", "tokio-macros", diff --git a/Cargo.toml b/Cargo.toml index c064211..502b4d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,10 +8,12 @@ edition = "2018" bincode = "1.3.1" dirs = "3.0.1" handlebars = "3.5.1" +hex = "0.4.2" +rand = "0.7.3" serde = { version = "1.0.117", features = ["derive"] } serde_json = "1.0.59" sha2 = "0.9.1" sled = "0.34.4" structopt = "0.3.20" -tokio = { version = "0.2.22", features = ["macros"] } +tokio = { version = "0.2.22", features = ["macros", "rt-threaded"] } warp = "0.2.5" diff --git a/src/db.rs b/src/db.rs index 1134d39..7e3a480 100644 --- a/src/db.rs +++ b/src/db.rs @@ -1,15 +1,21 @@ -use sled::{Db, IVec, Tree}; +use sled::{Db, Tree}; use std::path::Path; const DB_DIR: &'static str = "db"; +const DB_NAME_ADS: &'static str = "ads"; + pub struct Dbs { - db: Db, + pub db: Db, + pub ads: Tree, } pub fn load_dbs(path: &Path) -> Dbs { //std::fs::create_dir_all(path.join(DB_DIR)).expect("Cannot create db dir"); let db = sled::open(path.join(DB_DIR)).expect("Cannot open db"); - Dbs { db } + Dbs { + ads: db.open_tree(DB_NAME_ADS).unwrap(), + db, + } } diff --git a/src/server.rs b/src/server.rs index 86c072f..1f142e9 100644 --- a/src/server.rs +++ b/src/server.rs @@ -5,13 +5,36 @@ use serde::{Deserialize, Serialize}; use serde_json::value::{Map, Value as Json}; use sha2::{Digest, Sha512Trunc256}; use std::{ - convert::TryInto, + 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, @@ -20,16 +43,28 @@ pub async fn start_server( ) { let templates = Arc::new(templates); - let ads = vec![]; - let mut data = Map::::new(); data.insert("lang".into(), to_json("fr")); - data.insert("ads".into(), to_json(ads.clone())); - - let ads = Arc::new(RwLock::new(ads)); + data.insert( + "ads".into(), + 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 data = Arc::new(RwLock::new(data)); + let dbs = Arc::new(dbs); + let handle_index = { let data = data.clone(); move || { @@ -46,45 +81,132 @@ pub async fn start_server( .and(warp::get()) .and(warp::fs::dir(opt.dir.0.join(static_files::STATIC_DIR))); - let route_index = warp::path::end().and( - warp::get().map(handle_index.clone()).or(warp::post() - .and(warp::body::form::()) - .map(move |ad: NewAdQuery| { - let mut hasher = Sha512Trunc256::new(); - hasher.update(ad.password); - ads.write().unwrap().push(Ad { - author: ad.author, - password: hasher.finalize()[..].try_into().unwrap(), - pubkey: (!ad.pubkey.is_empty()).then_some(ad.pubkey), - time: 0, - title: ad.title, - }); - data.write() - .unwrap() - .insert("ads".into(), to_json(&*ads.read().unwrap())); - handle_index() - })), - ); + let handle_new_ad = { + let handle_index = handle_index.clone(); + let dbs = dbs.clone(); + let data = data.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(), + pubkey: (!query.pubkey.is_empty()).then_some(query.pubkey), + time: 0, + title: query.title, + }) + .unwrap(), + ) + .unwrap(); + dbs.ads.flush().unwrap(); + data.write().unwrap().insert( + "ads".into(), + 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::>(), + ), + ); + 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(); + data.write().unwrap().insert( + "ads".into(), + 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::>(), + ), + ); + } + } + } + } + } + handle_index() + } + }; + + let route_index = warp::path::end().and(warp::get().map(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(Debug, Deserialize)] -struct WotdQuery { - wotd: String, -} - #[derive(Clone, Debug, Deserialize)] struct NewAdQuery { author: String, - password: String, + psw: String, pubkey: String, title: String, } -#[derive(Clone, Debug, Serialize)] +#[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, @@ -92,3 +214,9 @@ struct Ad { time: u64, title: String, } + +#[derive(Clone, Debug, Serialize)] +struct AdWithId { + id: String, + ad: Ad, +} diff --git a/src/static_files.rs b/src/static_files.rs index 9ceeeea..e12d2be 100644 --- a/src/static_files.rs +++ b/src/static_files.rs @@ -5,6 +5,7 @@ static STATIC_FILES: &'static [(&str, &[u8])] = &[ ("style1.css", include_bytes!("../static/style1.css")), ("standgm.png", include_bytes!("../static/standgm.png")), ("banner.jpg", include_bytes!("../static/banner.jpg")), + ("icon.png", include_bytes!("../static/icon.png")), ]; pub fn init_static_files(dir: &Path) { diff --git a/static/icon.png b/static/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..82beb5365337ebefcbe62274796d3082c8093037 GIT binary patch literal 9555 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 I(Jtiw0L7jP9{>OV literal 0 HcmV?d00001 diff --git a/templates/index.html b/templates/index.html index 0d32aaa..2938434 100644 --- a/templates/index.html +++ b/templates/index.html @@ -4,6 +4,7 @@ ĞMarché +
@@ -13,33 +14,45 @@

Ceci est une démo du nouveau site ĞMarché en développement. C'est très moche et il n'y a pas tellement de fonctionnalités mais ça avance. ;)

- - - - - - {{#each ads}} - - - - - {{/each}} - -
AnnonceVendeur
{{this.title}}{{#if this.pubkey}}
{{this.author}}
{{this.pubkey}}
{{else}}{{this.author}}{{/if}}
+
+ + + + + + {{#each ads}} + + + + + + {{/each}} + +
AnnonceVendeur
{{this.ad.title}}{{#if this.ad.pubkey}}
{{this.ad.author}}
{{this.ad.pubkey}}
{{else}}{{this.ad.author}}{{/if}}
+ + +
+ Supprimer l'annonce sélectionnée + +
+ +
+
Marché
+
Nouvelle annonce - -
- -
- -
- -
+ +
+ +
+ +
+ +