Switched from warp to tide

This commit is contained in:
Pascal Engélibert 2020-12-17 10:05:26 +01:00
parent 9baa8c1a0f
commit 6c0af083d5
10 changed files with 1336 additions and 828 deletions

1314
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -5,18 +5,16 @@ authors = ["Pascal Engélibert <tuxmain@zettascript.org>"]
edition = "2018" edition = "2018"
[dependencies] [dependencies]
async-std = { version = "1.6.0", features = ["attributes"] }
bincode = "1.3.1" bincode = "1.3.1"
bs58 = "0.4.0" bs58 = "0.4.0"
dirs = "3.0.1" dirs = "3.0.1"
handlebars = "3.5.1" handlebars = "3.5.1"
hex = "0.4.2" hex = "0.4.2"
http = "0.2.1"
rand = "0.7.3" rand = "0.7.3"
serde = { version = "1.0.118", features = ["derive"] } serde = { version = "1.0.118", features = ["derive"] }
serde_json = "1.0.60" serde_json = "1.0.60"
sha2 = "0.9.2" sha2 = "0.9.2"
sled = "0.34.6" sled = "0.34.6"
structopt = "0.3.21" structopt = "0.3.21"
tokio = { version = "0.2.24", features = ["macros", "rt-threaded"] } tide = { version = "0.15.0", default-features = false, features = ["h1-server", "cookies", "logger"] }
urlencoding = "1.1.1"
warp = { version = "0.2.5", default-features = false }

View File

@ -3,6 +3,7 @@
mod cli; mod cli;
mod config; mod config;
mod db; mod db;
mod queries;
mod server; mod server;
mod static_files; mod static_files;
mod templates; mod templates;
@ -10,7 +11,7 @@ mod utils;
use structopt::StructOpt; use structopt::StructOpt;
#[tokio::main] #[async_std::main]
async fn main() { async fn main() {
let opt = cli::MainOpt::from_args(); let opt = cli::MainOpt::from_args();

50
src/queries.rs Normal file
View File

@ -0,0 +1,50 @@
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct NewAdQuery {
pub author: String,
pub price: String,
pub psw: String,
pub pubkey: String,
pub quantity: String,
pub title: String,
}
#[derive(Clone, Debug, Deserialize)]
pub struct RmAdQuery {
pub ad: String,
pub psw: String,
}
#[derive(Clone, Debug, Deserialize)]
pub struct AdminRmAdQuery {
pub ad: String,
}
#[derive(Clone, Debug, Deserialize)]
pub struct AdminLoginQuery {
pub psw: String,
}
#[derive(Clone, Debug, Deserialize)]
#[serde(tag = "a")]
pub enum Query {
#[serde(rename = "new_ad")]
NewAdQuery(NewAdQuery),
#[serde(rename = "rm_ad")]
RmAdQuery(RmAdQuery),
}
#[derive(Clone, Debug, Deserialize)]
#[serde(tag = "a")]
pub enum AdminQuery {
#[serde(rename = "login")]
LoginQuery(AdminLoginQuery),
#[serde(rename = "rm_ad")]
RmAdQuery(AdminRmAdQuery),
}
#[derive(Debug, Deserialize)]
struct SelectAdQuery {
pub ad: String,
}

View File

@ -1,430 +1,350 @@
use super::{cli, config, db, static_files, templates, utils::*}; use super::{cli, config, db, queries::*, static_files, templates::*, utils::*};
use handlebars::to_json; use handlebars::to_json;
use serde::{Deserialize, Serialize};
use serde_json::value::Value as Json;
use sha2::{Digest, Sha512Trunc256}; use sha2::{Digest, Sha512Trunc256};
use std::{ use std::{
convert::{TryFrom, TryInto}, convert::{TryFrom, TryInto},
sync::{Arc, RwLock}, sync::Arc,
}; };
use warp::Filter;
use warp::Reply;
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,
templates: templates::Templates<'static>, templates: Templates<'static>,
opt: cli::MainOpt, opt: cli::MainOpt,
) { ) {
let templates = Arc::new(templates); 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>(&ad).ok()?,
})
})
.collect::<Vec<AdWithId>>(),
)));
let config = Arc::new(config); let config = Arc::new(config);
let dbs = Arc::new(dbs); let dbs = Arc::new(dbs);
let handle_index = { let mut app = tide::new();
let cache_ads = cache_ads.clone(); app.at("/static")
.serve_dir(opt.dir.0.join(static_files::STATIC_DIR))
.unwrap();
app.at("/").get({
let templates = templates.clone(); let templates = templates.clone();
move |errors: &[ErrorTemplate], new_ad_form_refill| { let dbs = dbs.clone();
warp::reply::html( move |req: tide::Request<()>| serve_index(req, templates.clone(), dbs.clone(), &[], None)
templates });
.hb app.at("/").post({
.render(
"index.html",
&IndexTemplate {
lang: "fr",
errors,
ads: &*cache_ads.read().unwrap(),
new_ad_form_refill,
},
)
.unwrap_or_else(|e| e.to_string()),
)
.into_response()
}
};
let handle_admin_login = {
let templates = templates.clone(); let templates = templates.clone();
move |errors: &[ErrorTemplate]| {
warp::reply::html(
templates
.hb
.render(
"admin_login.html",
&AdminLoginTemplate { lang: "fr", errors },
)
.unwrap_or_else(|e| e.to_string()),
)
.into_response()
}
};
let handle_admin = {
let cache_ads = cache_ads.clone();
move |errors: &[ErrorTemplate]| {
warp::reply::html(
templates
.hb
.render(
"admin.html",
&AdminTemplate {
lang: "fr",
errors,
ads: &*cache_ads.read().unwrap(),
},
)
.unwrap_or_else(|e| e.to_string()),
)
.into_response()
}
};
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 dbs = dbs.clone();
let cache_ads = cache_ads.clone(); move |req: tide::Request<()>| handle_post_index(req, templates.clone(), dbs.clone())
move |query: NewAdQuery| { });
let mut hasher = Sha512Trunc256::new(); app.at("/ad/:ad").get({
hasher.update(&query.psw); let templates = templates.clone();
dbs.ads
.insert(
AdId::random(),
bincode::serialize(&Ad {
pubkey: if query.pubkey.is_empty() {
None
} else {
Some(match format_pubkey(&query.pubkey) {
Ok(pubkey) => pubkey,
Err(e) => return handle_index(&[ErrorTemplate{text: match e {
PubkeyDecodeError::BadChecksum => "La somme de contrôle de la clé publique est incorrecte.",
PubkeyDecodeError::BadFormat => "Le format de la clé publique est incorrect.",
}}], Some(query))
})
},
author: query.author,
password: hasher.finalize()[..].try_into().unwrap(),
price: query.price,
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>(&ad).ok()?,
})
})
.collect::<Vec<AdWithId>>(),
);
drop(cache_ads);
redirect_302("/").into_response()
}
};
let handle_rm_ad = {
let handle_index = handle_index.clone();
let dbs = dbs.clone(); let dbs = dbs.clone();
let cache_ads = cache_ads.clone(); move |req: tide::Request<()>| serve_index(req, templates.clone(), dbs.clone(), &[], None)
move |query: RmAdQuery| { });
let mut hasher = Sha512Trunc256::new(); app.at("/admin").get({
hasher.update(query.psw); let config = config.clone();
let password: PasswordHash = hasher.finalize()[..].try_into().unwrap(); let templates = templates.clone();
/*query let dbs = dbs.clone();
.ads move |req: tide::Request<()>| {
.into_iter() handle_admin(req, config.clone(), templates.clone(), dbs.clone())
.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();
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>(&ad).ok()?,
})
})
.collect::<Vec<AdWithId>>(),
);
} else {
return handle_index(
&[ErrorTemplate {
text: "Le mot de passe de l'annonce est incorrect.",
}],
None,
);
}
}
}
}
}
redirect_302("/").into_response()
} }
}; });
app.at("/admin").post({
let config = config.clone();
move |req: tide::Request<()>| {
handle_post_admin(req, config.clone(), templates.clone(), dbs.clone())
}
});
app.at("/admin/logout").get(handle_admin_logout);
app.listen(config.listen).await.unwrap();
}
let handle_admin_rm_ad = { async fn serve_index<'a>(
move |query: AdminRmAdQuery| { req: tide::Request<()>,
if let Ok(ad_id) = hex::decode(query.ad) { templates: Arc<Templates<'static>>,
if let Ok(ad_id) = AdId::try_from(ad_id.as_ref()) { dbs: Arc<db::Dbs>,
if dbs.ads.remove(&ad_id).unwrap().is_some() { errors: &[ErrorTemplate<'a>],
dbs.ads.flush().unwrap(); new_ad_form_refill: Option<NewAdQuery>,
let mut cache_ads = cache_ads.write().unwrap(); ) -> tide::Result<tide::Response> {
*cache_ads = to_json( let selected_ad = req.param("ad").ok();
Ok(tide::Response::builder(200)
.content_type(tide::http::mime::HTML)
.body(
templates
.hb
.render(
"index.html",
&IndexTemplate {
lang: "fr",
errors,
ads: &to_json(
dbs.ads dbs.ads
.iter() .iter()
.filter_map(|x| { .filter_map(|x| {
let (ad_id, ad) = x.ok()?; let (ad_id, ad) = x.ok()?;
let ad_id = hex::encode(ad_id.as_ref());
Some(AdWithId { Some(AdWithId {
id: hex::encode(ad_id.as_ref()),
ad: bincode::deserialize::<Ad>(&ad).ok()?, ad: bincode::deserialize::<Ad>(&ad).ok()?,
selected: selected_ad.map_or(false, |i| i == ad_id),
id: ad_id,
}) })
}) })
.collect::<Vec<AdWithId>>(), .collect::<Vec<AdWithId>>(),
); ),
new_ad_form_refill,
},
)
.unwrap_or_else(|e| e.to_string()),
)
.build())
}
async fn serve_admin<'a>(
req: tide::Request<()>,
templates: Arc<Templates<'static>>,
dbs: Arc<db::Dbs>,
errors: &[ErrorTemplate<'a>],
) -> tide::Result<tide::Response> {
let selected_ad = req.param("ad").ok();
Ok(tide::Response::builder(200)
.content_type(tide::http::mime::HTML)
.body(
templates
.hb
.render(
"admin.html",
&AdminTemplate {
lang: "fr",
errors,
ads: &to_json(
dbs.ads
.iter()
.filter_map(|x| {
let (ad_id, ad) = x.ok()?;
let ad_id = hex::encode(ad_id.as_ref());
Some(AdWithId {
ad: bincode::deserialize::<Ad>(&ad).ok()?,
selected: selected_ad.map_or(false, |i| i == ad_id),
id: ad_id,
})
})
.collect::<Vec<AdWithId>>(),
),
},
)
.unwrap_or_else(|e| e.to_string()),
)
.build())
}
async fn serve_admin_login<'a>(
_req: tide::Request<()>,
templates: Arc<Templates<'static>>,
errors: &[ErrorTemplate<'a>],
) -> tide::Result<tide::Response> {
Ok(tide::Response::builder(200)
.content_type(tide::http::mime::HTML)
.body(
templates
.hb
.render(
"admin_login.html",
&AdminLoginTemplate { lang: "fr", errors },
)
.unwrap_or_else(|e| e.to_string()),
)
.build())
}
async fn handle_post_index(
mut req: tide::Request<()>,
templates: Arc<Templates<'static>>,
dbs: Arc<db::Dbs>,
) -> tide::Result<tide::Response> {
match req.body_form::<Query>().await? {
Query::NewAdQuery(query) => handle_new_ad(req, templates.clone(), dbs.clone(), query).await,
Query::RmAdQuery(query) => handle_rm_ad(req, templates.clone(), dbs.clone(), query).await,
}
}
async fn handle_new_ad(
req: tide::Request<()>,
templates: Arc<Templates<'static>>,
dbs: Arc<db::Dbs>,
query: NewAdQuery,
) -> tide::Result<tide::Response> {
let mut hasher = Sha512Trunc256::new();
hasher.update(&query.psw);
dbs.ads
.insert(
AdId::random(),
bincode::serialize(&Ad {
pubkey: if query.pubkey.is_empty() {
None
} else {
Some(match format_pubkey(&query.pubkey) {
Ok(pubkey) => pubkey,
Err(e) => return serve_index(
req,
templates,
dbs,
&[ErrorTemplate {
text: match e {
PubkeyDecodeError::BadChecksum => {
"La somme de contrôle de la clé publique est incorrecte."
}
PubkeyDecodeError::BadFormat => {
"Le format de la clé publique est incorrect."
}
},
}],
Some(query),
)
.await,
})
},
author: query.author,
password: hasher.finalize()[..].try_into().unwrap(),
price: query.price,
quantity: query.quantity,
time: 0,
title: query.title,
})
.unwrap(),
)
.unwrap();
dbs.ads.flush().unwrap();
Ok(tide::Redirect::new("/").into())
}
async fn handle_rm_ad(
req: tide::Request<()>,
templates: Arc<Templates<'static>>,
dbs: Arc<db::Dbs>,
query: RmAdQuery,
) -> tide::Result<tide::Response> {
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();
} else {
return serve_index(
req,
templates,
dbs,
&[ErrorTemplate {
text: "Le mot de passe de l'annonce est incorrect.",
}],
None,
)
.await;
} }
} }
} }
redirect_302("/admin").into_response()
} }
}; }
Ok(tide::Redirect::new("/").into())
}
let route_index = warp::path::end().and(warp::get().map(move || handle_index(&[], None)).or( async fn handle_admin(
warp::post().and( req: tide::Request<()>,
warp::body::form::<Query>().map(move |query: Query| match query { config: Arc<config::Config>,
Query::NewAdQuery(query) => handle_new_ad(query), templates: Arc<Templates<'static>>,
Query::RmAdQuery(query) => handle_rm_ad(query), dbs: Arc<db::Dbs>,
}), ) -> tide::Result<tide::Response> {
), if let Some(psw) = req.cookie("admin") {
)); if config.admin_passwords.contains(&String::from(psw.value())) {
serve_admin(req, templates, dbs, &[]).await
let route_admin = warp::path("admin").and( } else {
warp::path::end() serve_admin_login(
.and( req,
warp::cookie("admin") templates,
.and(warp::body::form::<AdminQuery>()) &[ErrorTemplate {
.map({ text: "Mot de passe administrateur invalide",
let handle_admin_login = handle_admin_login.clone(); }],
let config = config.clone();
move |psw, query| {
if config.admin_passwords.contains(&psw) {
let AdminQuery::RmAdQuery(query) = query;
handle_admin_rm_ad(query)
} else {
handle_admin_login(&[ErrorTemplate {
text: "Mot de passe administrateur invalide",
}])
}
}
})
.or(warp::cookie("admin").map({
let handle_admin = handle_admin.clone();
let handle_admin_login = handle_admin_login.clone();
let config = config.clone();
move |psw| {
if config.admin_passwords.contains(&psw) {
handle_admin(&[])
} else {
handle_admin_login(&[ErrorTemplate {
text: "Mot de passe administrateur invalide",
}])
}
}
}))
.or(warp::path::end().map({
let handle_admin_login = handle_admin_login.clone();
move || handle_admin_login(&[])
})),
) )
.or(warp::path("login").and(warp::path::end()).and( .await
warp::post() }
.and(warp::get().map(move || handle_admin(&[]))) } else {
.or(warp::body::form::<AdminLoginQuery>().map({ serve_admin_login(req, templates, &[]).await
let config = config.clone(); }
move |query: AdminLoginQuery| { }
if config.admin_passwords.contains(&query.psw) {
warp::reply::with_header( async fn handle_post_admin(
redirect_302("/admin").into_response(), mut req: tide::Request<()>,
"Set-Cookie", config: Arc<config::Config>,
format!( templates: Arc<Templates<'static>>,
"admin={}; path=/; HttpOnly", dbs: Arc<db::Dbs>,
urlencoding::encode(&query.psw) ) -> tide::Result<tide::Response> {
), if let Some(psw) = req.cookie("admin") {
) if config.admin_passwords.contains(&String::from(psw.value())) {
.into_response() match req.body_form::<AdminQuery>().await? {
} else { AdminQuery::RmAdQuery(query) => {
handle_admin_login(&[ErrorTemplate { if let Ok(ad_id) = hex::decode(query.ad) {
text: "Mot de passe administrateur invalide", if let Ok(ad_id) = AdId::try_from(ad_id.as_ref()) {
}]) dbs.ads.remove(&ad_id).unwrap();
.into_response()
} dbs.ads.flush().unwrap();
} }
})), }
)) Ok(tide::Redirect::new("/admin").into())
.or(warp::path("logout").and(warp::path::end()).map(|| { }
warp::reply::with_header( _ => serve_admin(req, templates, dbs, &[]).await,
redirect_302("/").into_response(), }
"Set-Cookie", } else {
"admin=; HttpOnly; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT", serve_admin_login(
) req,
.into_response() templates,
})), &[ErrorTemplate {
); text: "Mot de passe administrateur invalide",
}],
warp::serve(route_static.or(route_index).or(route_admin)) )
.run(config.listen) .await
.await; }
} else if let AdminQuery::LoginQuery(query) = req.body_form::<AdminQuery>().await? {
if config.admin_passwords.contains(&query.psw) {
serve_admin(req, templates, dbs, &[]).await.map(|mut r| {
let mut cookie = tide::http::Cookie::new("admin", query.psw);
cookie.set_http_only(Some(true));
cookie.set_path("/");
r.insert_cookie(cookie);
r
})
} else {
serve_admin_login(
req,
templates,
&[ErrorTemplate {
text: "Mot de passe administrateur invalide",
}],
)
.await
}
} else {
serve_admin_login(req, templates, &[]).await
}
} }
#[derive(Serialize)] async fn handle_admin_logout(req: tide::Request<()>) -> tide::Result<tide::Response> {
struct IndexTemplate<'a> { let mut r: tide::Response = tide::Redirect::new("/").into();
lang: &'a str, if let Some(mut cookie) = req.cookie("admin") {
errors: &'a [ErrorTemplate<'a>], cookie.set_path("/");
ads: &'a Json, r.remove_cookie(cookie);
new_ad_form_refill: Option<NewAdQuery>, }
} Ok(r)
#[derive(Serialize)]
struct AdminLoginTemplate<'a> {
lang: &'a str,
errors: &'a [ErrorTemplate<'a>],
}
#[derive(Serialize)]
struct AdminTemplate<'a> {
lang: &'a str,
errors: &'a [ErrorTemplate<'a>],
ads: &'a Json,
}
#[derive(Serialize)]
struct ErrorTemplate<'a> {
text: &'a str,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
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)]
struct AdminRmAdQuery {
ad: String,
}
#[derive(Clone, Debug, Deserialize)]
struct AdminLoginQuery {
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)]
#[serde(tag = "a")]
enum AdminQuery {
#[serde(rename = "rm_ad")]
RmAdQuery(AdminRmAdQuery),
}
#[derive(Clone, Debug, Deserialize, Serialize)]
struct Ad {
author: String,
password: PasswordHash,
price: String,
pubkey: Option<String>,
quantity: String,
time: u64,
title: String,
}
#[derive(Clone, Debug, Serialize)]
struct AdWithId {
id: String,
ad: Ad,
} }

View File

@ -1,4 +1,8 @@
use super::queries::*;
use handlebars::Handlebars; use handlebars::Handlebars;
use serde::Serialize;
use serde_json::value::Value as Json;
use std::path::Path; use std::path::Path;
const TEMPLATES_DIR: &str = "templates"; const TEMPLATES_DIR: &str = "templates";
@ -41,3 +45,29 @@ pub fn load_templates<'reg>(dir: &Path) -> Templates<'reg> {
} }
Templates { hb } Templates { hb }
} }
#[derive(Serialize)]
pub struct IndexTemplate<'a> {
pub lang: &'a str,
pub errors: &'a [ErrorTemplate<'a>],
pub ads: &'a Json,
pub new_ad_form_refill: Option<NewAdQuery>,
}
#[derive(Serialize)]
pub struct AdminLoginTemplate<'a> {
pub lang: &'a str,
pub errors: &'a [ErrorTemplate<'a>],
}
#[derive(Serialize)]
pub struct AdminTemplate<'a> {
pub lang: &'a str,
pub errors: &'a [ErrorTemplate<'a>],
pub ads: &'a Json,
}
#[derive(Serialize)]
pub struct ErrorTemplate<'a> {
pub text: &'a str,
}

View File

@ -1,4 +1,49 @@
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use std::convert::{TryFrom, TryInto};
pub type PasswordHash = [u8; 32];
#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize)]
pub struct AdId([u8; 16]);
impl AdId {
pub 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()?))
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct Ad {
pub author: String,
pub password: PasswordHash,
pub price: String,
pub pubkey: Option<String>,
pub quantity: String,
pub time: u64,
pub title: String,
}
#[derive(Clone, Debug, Serialize)]
pub struct AdWithId {
pub id: String,
pub ad: Ad,
pub selected: bool,
}
pub enum PubkeyDecodeError { pub enum PubkeyDecodeError {
BadChecksum, BadChecksum,
@ -39,10 +84,3 @@ pub fn format_pubkey(raw: &str) -> Result<String, PubkeyDecodeError> {
&checksum[..3] &checksum[..3]
)) ))
} }
pub fn redirect_302(url: &str) -> warp::reply::WithStatus<warp::reply::WithHeader<&str>> {
warp::reply::with_status(
warp::reply::with_header("", "Location", url),
http::status::StatusCode::FOUND,
)
}

View File

@ -38,7 +38,7 @@
{{#each ads}} {{#each ads}}
<tr> <tr>
<td><input type="radio" name="ad" value="{{this.id}}" aria-label="Sélectionner l'annonce" required/></td> <td><input type="radio" name="ad" value="{{this.id}}" aria-label="Sélectionner l'annonce" required/></td>
<td onclick="ad_detail(event,'{{this.id}}')" title="Afficher le détail"><a href="ad/{{this.id}}">{{this.ad.title}}</a></td> <td onclick="ad_detail(event,'{{this.id}}')" title="Afficher le détail"><a href="/admin/ad/{{this.id}}">{{this.ad.title}}</a></td>
<td>{{this.ad.quantity}}</td> <td>{{this.ad.quantity}}</td>
<td>{{this.ad.author}}</td> <td>{{this.ad.author}}</td>
</tr> </tr>

View File

@ -28,9 +28,10 @@
</div> </div>
{{/if}} {{/if}}
<form action="/admin/login" method="post"> <form action="/admin" method="post">
<input type="hidden" name="a" value="login" autocomplete="off"/>
<label for="f_psw">Mot de passe&nbsp;:</label> <label for="f_psw">Mot de passe&nbsp;:</label>
<input type="password" id="f_psw" name="psw"/> <input type="password" id="f_psw" name="psw" autofocus/>
<input type="submit" value="S'authentifier"/> <input type="submit" value="S'authentifier"/>
</form> </form>
</main> </main>

View File

@ -42,11 +42,11 @@
{{#each ads}} {{#each ads}}
<tr> <tr>
<td><input type="radio" name="ad" value="{{this.id}}" aria-label="Sélectionner l'annonce" required/></td> <td><input type="radio" name="ad" value="{{this.id}}" aria-label="Sélectionner l'annonce" required/></td>
<td onclick="ad_detail(event,'{{this.id}}')" title="Afficher le détail"><a href="ad/{{this.id}}">{{this.ad.title}}</a></td> <td onclick="ad_detail(event,'{{this.id}}')" title="Afficher le détail"><a href="/ad/{{this.id}}">{{this.ad.title}}</a></td>
<td>{{this.ad.quantity}}</td> <td>{{this.ad.quantity}}</td>
<td>{{this.ad.author}}</td> <td>{{this.ad.author}}</td>
</tr> </tr>
<tr id="ad-detail-{{this.id}}" class="ad-detail ad-detail-no"> <tr id="ad-detail-{{this.id}}" class="ad-detail{{#unless this.selected}} ad-detail-no{{/unless}}">
<td colspan="4"><p> <td colspan="4"><p>
{{#if this.ad.pubkey}}Clé publique&nbsp;: {{this.ad.pubkey}}<br/>{{/if}} {{#if this.ad.pubkey}}Clé publique&nbsp;: {{this.ad.pubkey}}<br/>{{/if}}
Prix&nbsp;: {{this.ad.price}} Prix&nbsp;: {{this.ad.price}}