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",
"dirs",
"handlebars",
"hex",
"rand 0.7.3",
"serde",
"serde_json",
"sha2",
@ -519,6 +521,12 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@ -779,6 +787,16 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@ -1416,6 +1434,7 @@ dependencies = [
"lazy_static",
"memchr",
"mio",
"num_cpus",
"pin-project-lite",
"slab",
"tokio-macros",

View File

@ -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"

View File

@ -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,
}
}

View File

@ -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<Self, Self::Error> {
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::<String, Json>::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>(&ad).ok()?,
})
})
.collect::<Vec<AdWithId>>(),
),
);
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::<NewAdQuery>())
.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>(&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))
.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,
}

View File

@ -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) {

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"/>
<title>ĞMarché</title>
<link rel="stylesheet" href="/static/style1.css"/>
<link rel="shortcut icon" href="/static/icon.png"/>
</head>
<body>
<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>
<table border="1">
<thead>
<tr><td>Annonce</td><td>Vendeur</td></tr>
</thead>
<tbody>
{{#each ads}}
<tr>
<td>{{this.title}}</td>
<td>{{#if this.pubkey}}<details><summary>{{this.author}}</summary><br/>{{this.pubkey}}</details>{{else}}{{this.author}}{{/if}}</td>
</tr>
{{/each}}
</tbody>
</table>
<form method="post">
<table border="1">
<thead>
<tr><td></td><td>Annonce</td><td>Vendeur</td></tr>
</thead>
<tbody>
{{#each ads}}
<tr>
<td><input type="radio" name="ad" value="{{this.id}}"/></td>
<td>{{this.ad.title}}</td>
<td>{{#if this.ad.pubkey}}<details><summary>{{this.ad.author}}</summary><br/>{{this.ad.pubkey}}</details>{{else}}{{this.ad.author}}{{/if}}</td>
</tr>
{{/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"/>
<form method="post">
<input type="hidden" name="a" value="new_ad" autocomplete="off"/>
<fieldset>
<legend>Nouvelle annonce</legend>
<label for="f_title">Titre&nbsp;:</label>
<input type="text" id="f_title" name="title"/><br />
<label for="f_author">Votre nom&nbsp;:</label>
<input type="text" id="f_author" name="author"/><br />
<label for="f_pubkey">Clé publique&nbsp;:</label>
<input type="text" id="f_pubkey" name="pubkey"/><br />
<label for="f_password">Mot de passe&nbsp;:</label>
<input type="text" id="f_password" name="password"/><br />
<label for="f_new_title">Titre&nbsp;:</label>
<input type="text" id="f_new_title" name="title"/><br />
<label for="f_new_author">Votre nom&nbsp;:</label>
<input type="text" id="f_new_author" name="author"/><br />
<label for="f_new_pubkey">Clé publique&nbsp;:</label>
<input type="text" id="f_new_pubkey" name="pubkey"/><br />
<label for="f_new_psw">Mot de passe&nbsp;:</label>
<input type="text" id="f_new_psw" name="psw"/><br />
<input type="submit" value="Publier"/>
</fieldset>
</form>