Ads db, new/rm, better query handling, icon

This commit is contained in:
Pascal Engélibert 2020-11-02 10:57:50 +01:00
parent 6a75946509
commit bcfccd6a0e
7 changed files with 227 additions and 58 deletions

19
Cargo.lock generated
View File

@ -426,6 +426,8 @@ dependencies = [
"bincode", "bincode",
"dirs", "dirs",
"handlebars", "handlebars",
"hex",
"rand 0.7.3",
"serde", "serde",
"serde_json", "serde_json",
"sha2", "sha2",
@ -518,6 +520,12 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "hex"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35"
[[package]] [[package]]
name = "http" name = "http"
version = "0.2.1" version = "0.2.1"
@ -778,6 +786,16 @@ dependencies = [
"winapi 0.3.9", "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]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.4.1" version = "1.4.1"
@ -1416,6 +1434,7 @@ dependencies = [
"lazy_static", "lazy_static",
"memchr", "memchr",
"mio", "mio",
"num_cpus",
"pin-project-lite", "pin-project-lite",
"slab", "slab",
"tokio-macros", "tokio-macros",

View File

@ -8,10 +8,12 @@ edition = "2018"
bincode = "1.3.1" bincode = "1.3.1"
dirs = "3.0.1" dirs = "3.0.1"
handlebars = "3.5.1" handlebars = "3.5.1"
hex = "0.4.2"
rand = "0.7.3"
serde = { version = "1.0.117", features = ["derive"] } serde = { version = "1.0.117", features = ["derive"] }
serde_json = "1.0.59" serde_json = "1.0.59"
sha2 = "0.9.1" sha2 = "0.9.1"
sled = "0.34.4" sled = "0.34.4"
structopt = "0.3.20" structopt = "0.3.20"
tokio = { version = "0.2.22", features = ["macros"] } tokio = { version = "0.2.22", features = ["macros", "rt-threaded"] }
warp = "0.2.5" warp = "0.2.5"

View File

@ -1,15 +1,21 @@
use sled::{Db, IVec, Tree}; use sled::{Db, Tree};
use std::path::Path; use std::path::Path;
const DB_DIR: &'static str = "db"; const DB_DIR: &'static str = "db";
const DB_NAME_ADS: &'static str = "ads";
pub struct Dbs { pub struct Dbs {
db: Db, pub db: Db,
pub ads: Tree,
} }
pub fn load_dbs(path: &Path) -> Dbs { pub fn load_dbs(path: &Path) -> Dbs {
//std::fs::create_dir_all(path.join(DB_DIR)).expect("Cannot create db dir"); //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"); let db = sled::open(path.join(DB_DIR)).expect("Cannot open db");
Dbs { db } Dbs {
ads: db.open_tree(DB_NAME_ADS).unwrap(),
db,
}
} }

View File

@ -5,13 +5,36 @@ use serde::{Deserialize, Serialize};
use serde_json::value::{Map, Value as Json}; use serde_json::value::{Map, Value as Json};
use sha2::{Digest, Sha512Trunc256}; use sha2::{Digest, Sha512Trunc256};
use std::{ use std::{
convert::TryInto, convert::{TryFrom, TryInto},
sync::{Arc, RwLock}, sync::{Arc, RwLock},
}; };
use warp::Filter; use warp::Filter;
type PasswordHash = [u8; 32]; 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<Self, Self::Error> {
Ok(Self(v.try_into()?))
}
}
pub async fn start_server( pub async fn start_server(
config: config::Config, config: config::Config,
dbs: db::Dbs, dbs: db::Dbs,
@ -20,16 +43,28 @@ pub async fn start_server(
) { ) {
let templates = Arc::new(templates); let templates = Arc::new(templates);
let ads = vec![];
let mut data = Map::<String, Json>::new(); let mut data = Map::<String, Json>::new();
data.insert("lang".into(), to_json("fr")); data.insert("lang".into(), to_json("fr"));
data.insert("ads".into(), to_json(ads.clone())); data.insert(
"ads".into(),
let ads = Arc::new(RwLock::new(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>(&ad).ok()?,
})
})
.collect::<Vec<AdWithId>>(),
),
);
let data = Arc::new(RwLock::new(data)); let data = Arc::new(RwLock::new(data));
let dbs = Arc::new(dbs);
let handle_index = { let handle_index = {
let data = data.clone(); let data = data.clone();
move || { move || {
@ -46,45 +81,132 @@ pub async fn start_server(
.and(warp::get()) .and(warp::get())
.and(warp::fs::dir(opt.dir.0.join(static_files::STATIC_DIR))); .and(warp::fs::dir(opt.dir.0.join(static_files::STATIC_DIR)));
let route_index = warp::path::end().and( let handle_new_ad = {
warp::get().map(handle_index.clone()).or(warp::post() let handle_index = handle_index.clone();
.and(warp::body::form::<NewAdQuery>()) let dbs = dbs.clone();
.map(move |ad: NewAdQuery| { let data = data.clone();
let mut hasher = Sha512Trunc256::new(); move |query: NewAdQuery| {
hasher.update(ad.password); let mut hasher = Sha512Trunc256::new();
ads.write().unwrap().push(Ad { hasher.update(query.psw);
author: ad.author, dbs.ads
password: hasher.finalize()[..].try_into().unwrap(), .insert(
pubkey: (!ad.pubkey.is_empty()).then_some(ad.pubkey), AdId::random(),
time: 0, bincode::serialize(&Ad {
title: ad.title, author: query.author,
}); password: hasher.finalize()[..].try_into().unwrap(),
data.write() pubkey: (!query.pubkey.is_empty()).then_some(query.pubkey),
.unwrap() time: 0,
.insert("ads".into(), to_json(&*ads.read().unwrap())); title: query.title,
handle_index() })
})), .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>(&ad).ok()?,
})
})
.collect::<Vec<AdWithId>>(),
),
);
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::<Ad>(&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::<Ad>(&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>(&ad).ok()?,
})
})
.collect::<Vec<AdWithId>>(),
),
);
}
}
}
}
}
handle_index()
}
};
let route_index = warp::path::end().and(warp::get().map(handle_index).or(warp::post().and(
warp::body::form::<Query>().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)) warp::serve(route_static.or(route_index))
.run(config.listen) .run(config.listen)
.await; .await;
} }
#[derive(Debug, Deserialize)]
struct WotdQuery {
wotd: String,
}
#[derive(Clone, Debug, Deserialize)] #[derive(Clone, Debug, Deserialize)]
struct NewAdQuery { struct NewAdQuery {
author: String, author: String,
password: String, psw: String,
pubkey: String, pubkey: String,
title: 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 { struct Ad {
author: String, author: String,
password: PasswordHash, password: PasswordHash,
@ -92,3 +214,9 @@ struct Ad {
time: u64, time: u64,
title: String, title: String,
} }
#[derive(Clone, Debug, Serialize)]
struct AdWithId {
id: String,
ad: Ad,
}

View File

@ -5,6 +5,7 @@ static STATIC_FILES: &'static [(&str, &[u8])] = &[
("style1.css", include_bytes!("../static/style1.css")), ("style1.css", include_bytes!("../static/style1.css")),
("standgm.png", include_bytes!("../static/standgm.png")), ("standgm.png", include_bytes!("../static/standgm.png")),
("banner.jpg", include_bytes!("../static/banner.jpg")), ("banner.jpg", include_bytes!("../static/banner.jpg")),
("icon.png", include_bytes!("../static/icon.png")),
]; ];
pub fn init_static_files(dir: &Path) { pub fn init_static_files(dir: &Path) {

BIN
static/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

@ -4,6 +4,7 @@
<meta charset="utf-8"/> <meta charset="utf-8"/>
<title>ĞMarché</title> <title>ĞMarché</title>
<link rel="stylesheet" href="/static/style1.css"/> <link rel="stylesheet" href="/static/style1.css"/>
<link rel="shortcut icon" href="/static/icon.png"/>
</head> </head>
<body> <body>
<div class="center"> <div class="center">
@ -13,33 +14,45 @@
<p>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. ;)</p> <p>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. ;)</p>
<table border="1"> <form method="post">
<thead> <table border="1">
<tr><td>Annonce</td><td>Vendeur</td></tr> <thead>
</thead> <tr><td></td><td>Annonce</td><td>Vendeur</td></tr>
<tbody> </thead>
{{#each ads}} <tbody>
<tr> {{#each ads}}
<td>{{this.title}}</td> <tr>
<td>{{#if this.pubkey}}<details><summary>{{this.author}}</summary><br/>{{this.pubkey}}</details>{{else}}{{this.author}}{{/if}}</td> <td><input type="radio" name="ad" value="{{this.id}}"/></td>
</tr> <td>{{this.ad.title}}</td>
{{/each}} <td>{{#if this.ad.pubkey}}<details><summary>{{this.ad.author}}</summary><br/>{{this.ad.pubkey}}</details>{{else}}{{this.ad.author}}{{/if}}</td>
</tbody> </tr>
</table> {{/each}}
</tbody>
</table>
<input type="hidden" name="a" value="rm_ad" autocomplete="off"/>
<fieldset>
<legend>Supprimer l'annonce sélectionnée</legend>
<label for="f_rm_psw">Mot de passe&nbsp;:</label>
<input type="password" id="f_rm_psw" name="psw"/><br/>
<input type="submit" value="Supprimer"/>
</fieldset>
</form>
<img id="stand" alt="Marché" src="/static/standgm.png"/> <img id="stand" alt="Marché" src="/static/standgm.png"/>
<form method="post"> <form method="post">
<input type="hidden" name="a" value="new_ad" autocomplete="off"/>
<fieldset> <fieldset>
<legend>Nouvelle annonce</legend> <legend>Nouvelle annonce</legend>
<label for="f_title">Titre&nbsp;:</label> <label for="f_new_title">Titre&nbsp;:</label>
<input type="text" id="f_title" name="title"/><br /> <input type="text" id="f_new_title" name="title"/><br />
<label for="f_author">Votre nom&nbsp;:</label> <label for="f_new_author">Votre nom&nbsp;:</label>
<input type="text" id="f_author" name="author"/><br /> <input type="text" id="f_new_author" name="author"/><br />
<label for="f_pubkey">Clé publique&nbsp;:</label> <label for="f_new_pubkey">Clé publique&nbsp;:</label>
<input type="text" id="f_pubkey" name="pubkey"/><br /> <input type="text" id="f_new_pubkey" name="pubkey"/><br />
<label for="f_password">Mot de passe&nbsp;:</label> <label for="f_new_psw">Mot de passe&nbsp;:</label>
<input type="text" id="f_password" name="password"/><br /> <input type="text" id="f_new_psw" name="psw"/><br />
<input type="submit" value="Publier"/> <input type="submit" value="Publier"/>
</fieldset> </fieldset>
</form> </form>