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 0000000..82beb53 Binary files /dev/null and b/static/icon.png differ 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 - -
- -
- -
- -
+ +
+ +
+ +
+ +