aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/assets.rs12
-rw-r--r--src/controller/balance.rs6
-rw-r--r--src/controller/categories.rs20
-rw-r--r--src/controller/error.rs10
-rw-r--r--src/controller/incomes.rs23
-rw-r--r--src/controller/login.rs14
-rw-r--r--src/controller/payments.rs22
-rw-r--r--src/controller/statistics.rs6
-rw-r--r--src/controller/utils.rs30
-rw-r--r--src/main.rs51
-rw-r--r--src/routes.rs25
11 files changed, 128 insertions, 91 deletions
diff --git a/src/assets.rs b/src/assets.rs
index 80f9630..36fab55 100644
--- a/src/assets.rs
+++ b/src/assets.rs
@@ -2,6 +2,7 @@ use sha2::{Digest, Sha256};
use std::collections::HashMap;
use std::fs;
use std::iter::FromIterator;
+use std::fmt::Write;
pub fn get() -> HashMap<String, String> {
let paths = fs::read_dir("assets").unwrap().map(|e| {
@@ -19,9 +20,10 @@ pub fn get() -> HashMap<String, String> {
fn sha256(input: Vec<u8>) -> String {
let mut hasher = Sha256::new();
hasher.update(input);
- hasher
- .finalize()
- .iter()
- .map(|b| format!("{:x}", b))
- .collect()
+
+ let mut res = String::new();
+ for byte in hasher.finalize().iter() {
+ write!(&mut res, "{:x}", byte).unwrap();
+ }
+ res
}
diff --git a/src/controller/balance.rs b/src/controller/balance.rs
index adde8a9..c5d9d4a 100644
--- a/src/controller/balance.rs
+++ b/src/controller/balance.rs
@@ -1,4 +1,6 @@
-use hyper::{Body, Response};
+use http_body_util::Full;
+use hyper::body::Bytes;
+use hyper::Response;
use std::collections::HashMap;
use tera::Context;
@@ -9,7 +11,7 @@ use crate::model::user::User;
use crate::payer;
use crate::templates;
-pub async fn get(wallet: &Wallet) -> Response<Body> {
+pub async fn get(wallet: &Wallet) -> Response<Full<Bytes>> {
let users = db::users::list(&wallet.pool).await;
let incomes_from = db::incomes::defined_for_all(&wallet.pool).await;
diff --git a/src/controller/categories.rs b/src/controller/categories.rs
index b1a3664..ff2d8e7 100644
--- a/src/controller/categories.rs
+++ b/src/controller/categories.rs
@@ -1,4 +1,6 @@
-use hyper::{Body, Response};
+use http_body_util::Full;
+use hyper::body::Bytes;
+use hyper::Response;
use std::collections::HashMap;
use tera::Context;
@@ -12,7 +14,7 @@ use crate::validation;
pub async fn table(
wallet: &Wallet,
query: queries::Categories,
-) -> Response<Body> {
+) -> Response<Full<Bytes>> {
let categories = db::categories::list(&wallet.pool).await;
let mut context = Context::new();
@@ -29,7 +31,7 @@ pub async fn table(
)
}
-pub async fn create_form(wallet: &Wallet) -> Response<Body> {
+pub async fn create_form(wallet: &Wallet) -> Response<Full<Bytes>> {
create_form_feedback(wallet, HashMap::new(), None).await
}
@@ -37,7 +39,7 @@ async fn create_form_feedback(
wallet: &Wallet,
form: HashMap<String, String>,
error: Option<String>,
-) -> Response<Body> {
+) -> Response<Full<Bytes>> {
let mut context = Context::new();
context.insert("header", &templates::Header::Categories);
context.insert("connected_user", &wallet.user.clone());
@@ -55,7 +57,7 @@ async fn create_form_feedback(
pub async fn create(
wallet: &Wallet,
form: HashMap<String, String>,
-) -> Response<Body> {
+) -> Response<Full<Bytes>> {
let error = |e: &str| {
create_form_feedback(wallet, form.clone(), Some(e.to_string()))
};
@@ -73,7 +75,7 @@ pub async fn create(
}
}
-pub async fn update_form(id: i64, wallet: &Wallet) -> Response<Body> {
+pub async fn update_form(id: i64, wallet: &Wallet) -> Response<Full<Bytes>> {
update_form_feedback(id, wallet, HashMap::new(), None).await
}
@@ -82,7 +84,7 @@ async fn update_form_feedback(
wallet: &Wallet,
form: HashMap<String, String>,
error: Option<String>,
-) -> Response<Body> {
+) -> Response<Full<Bytes>> {
let category = db::categories::get(&wallet.pool, id).await;
let is_category_used =
db::payments::is_category_used(&wallet.pool, id).await;
@@ -108,7 +110,7 @@ pub async fn update(
id: i64,
wallet: &Wallet,
form: HashMap<String, String>,
-) -> Response<Body> {
+) -> Response<Full<Bytes>> {
let error = |e: &str| {
update_form_feedback(id, wallet, form.clone(), Some(e.to_string()))
};
@@ -126,7 +128,7 @@ pub async fn update(
}
}
-pub async fn delete(id: i64, wallet: &Wallet) -> Response<Body> {
+pub async fn delete(id: i64, wallet: &Wallet) -> Response<Full<Bytes>> {
if db::categories::delete(&wallet.pool, id).await {
utils::redirect("/categories")
} else {
diff --git a/src/controller/error.rs b/src/controller/error.rs
index 2a84255..0f6dcc1 100644
--- a/src/controller/error.rs
+++ b/src/controller/error.rs
@@ -1,5 +1,7 @@
+use http_body_util::Full;
+use hyper::body::Bytes;
use hyper::header::CACHE_CONTROL;
-use hyper::{Body, Response};
+use hyper::Response;
use std::collections::HashMap;
use tera::{Context, Tera};
@@ -7,7 +9,11 @@ use crate::controller::utils;
use crate::controller::wallet::Wallet;
// TODO error code
-pub fn error(wallet: &Wallet, title: &str, message: &str) -> Response<Body> {
+pub fn error(
+ wallet: &Wallet,
+ title: &str,
+ message: &str,
+) -> Response<Full<Bytes>> {
utils::with_headers(
Response::new(
template(&wallet.assets, &wallet.templates, title, message).into(),
diff --git a/src/controller/incomes.rs b/src/controller/incomes.rs
index cc66ed6..f22098b 100644
--- a/src/controller/incomes.rs
+++ b/src/controller/incomes.rs
@@ -1,6 +1,8 @@
use chrono::Datelike;
use chrono::Utc;
-use hyper::{Body, Response};
+use http_body_util::Full;
+use hyper::body::Bytes;
+use hyper::Response;
use std::collections::HashMap;
use tera::Context;
@@ -13,7 +15,10 @@ use crate::validation;
static PER_PAGE: i64 = 10;
-pub async fn table(wallet: &Wallet, query: queries::Incomes) -> Response<Body> {
+pub async fn table(
+ wallet: &Wallet,
+ query: queries::Incomes,
+) -> Response<Full<Bytes>> {
let page = query.page.unwrap_or(1);
let count = db::incomes::count(&wallet.pool).await;
let incomes = db::incomes::list(&wallet.pool, page, PER_PAGE).await;
@@ -53,7 +58,7 @@ static MONTHS: [&str; 12] = [
pub async fn create_form(
wallet: &Wallet,
query: queries::Incomes,
-) -> Response<Body> {
+) -> Response<Full<Bytes>> {
create_form_feedback(wallet, query, HashMap::new(), None).await
}
@@ -62,7 +67,7 @@ async fn create_form_feedback(
query: queries::Incomes,
form: HashMap<String, String>,
error: Option<String>,
-) -> Response<Body> {
+) -> Response<Full<Bytes>> {
let users = db::users::list(&wallet.pool).await;
let mut context = Context::new();
@@ -87,7 +92,7 @@ pub async fn create(
wallet: &Wallet,
query: queries::Incomes,
form: HashMap<String, String>,
-) -> Response<Body> {
+) -> Response<Full<Bytes>> {
let error = |e: &str| {
create_form_feedback(wallet, query, form.clone(), Some(e.to_string()))
};
@@ -125,7 +130,7 @@ pub async fn update_form(
id: i64,
wallet: &Wallet,
query: queries::Incomes,
-) -> Response<Body> {
+) -> Response<Full<Bytes>> {
update_form_feedback(id, wallet, query, HashMap::new(), None).await
}
@@ -135,7 +140,7 @@ async fn update_form_feedback(
query: queries::Incomes,
form: HashMap<String, String>,
error: Option<String>,
-) -> Response<Body> {
+) -> Response<Full<Bytes>> {
let users = db::users::list(&wallet.pool).await;
let income = db::incomes::get(&wallet.pool, id).await;
@@ -163,7 +168,7 @@ pub async fn update(
wallet: &Wallet,
query: queries::Incomes,
form: HashMap<String, String>,
-) -> Response<Body> {
+) -> Response<Full<Bytes>> {
let error = |e: &str| {
update_form_feedback(
id,
@@ -203,7 +208,7 @@ pub async fn delete(
id: i64,
wallet: &Wallet,
query: queries::Incomes,
-) -> Response<Body> {
+) -> Response<Full<Bytes>> {
if db::incomes::delete(&wallet.pool, id).await {
utils::redirect(&format!("/incomes?page={}", query.page.unwrap_or(1)))
} else {
diff --git a/src/controller/login.rs b/src/controller/login.rs
index 036e6fc..dd50a3a 100644
--- a/src/controller/login.rs
+++ b/src/controller/login.rs
@@ -1,6 +1,8 @@
use bcrypt;
+use http_body_util::Full;
+use hyper::body::Bytes;
use hyper::header::SET_COOKIE;
-use hyper::{Body, Response};
+use hyper::Response;
use sqlx::sqlite::SqlitePool;
use std::collections::HashMap;
use tera::{Context, Tera};
@@ -18,7 +20,7 @@ pub async fn page(
assets: &HashMap<String, String>,
templates: &Tera,
error: Option<&str>,
-) -> Response<Body> {
+) -> Response<Full<Bytes>> {
let connected_user: Option<User> = None;
let mut context = Context::new();
@@ -34,7 +36,7 @@ pub async fn login(
templates: &Tera,
form: HashMap<String, String>,
pool: SqlitePool,
-) -> Response<Body> {
+) -> Response<Full<Bytes>> {
match validation::login::login(&form) {
Some(login) => {
match db::users::get_password_hash(&pool, login.email.clone()).await
@@ -88,14 +90,14 @@ async fn server_error(
assets: &HashMap<String, String>,
templates: &Tera,
msg: &str,
-) -> Response<Body> {
+) -> Response<Full<Bytes>> {
page(assets, templates, Some(msg)).await
}
async fn not_authorized(
assets: &HashMap<String, String>,
templates: &Tera,
-) -> Response<Body> {
+) -> Response<Full<Bytes>> {
page(
assets,
templates,
@@ -104,7 +106,7 @@ async fn not_authorized(
.await
}
-pub async fn logout(config: &Config, wallet: &Wallet) -> Response<Body> {
+pub async fn logout(config: &Config, wallet: &Wallet) -> Response<Full<Bytes>> {
if db::users::remove_login_token(&wallet.pool, wallet.user.id).await {
with_headers(
utils::redirect("/"),
diff --git a/src/controller/payments.rs b/src/controller/payments.rs
index 2663fa7..8184015 100644
--- a/src/controller/payments.rs
+++ b/src/controller/payments.rs
@@ -1,5 +1,7 @@
+use http_body_util::Full;
+use hyper::body::Bytes;
use hyper::header::CONTENT_TYPE;
-use hyper::{Body, Response};
+use hyper::Response;
use std::collections::HashMap;
use tera::Context;
@@ -16,7 +18,7 @@ static PER_PAGE: i64 = 10;
pub async fn table(
wallet: &Wallet,
query: queries::Payments,
-) -> Response<Body> {
+) -> Response<Full<Bytes>> {
let page = query.page.unwrap_or(1);
let count = db::payments::count(&wallet.pool, &query).await;
let payments =
@@ -48,7 +50,7 @@ pub async fn table(
pub async fn create_form(
wallet: &Wallet,
query: queries::Payments,
-) -> Response<Body> {
+) -> Response<Full<Bytes>> {
create_form_feedback(wallet, query, HashMap::new(), None).await
}
@@ -57,7 +59,7 @@ async fn create_form_feedback(
query: queries::Payments,
form: HashMap<String, String>,
error: Option<String>,
-) -> Response<Body> {
+) -> Response<Full<Bytes>> {
let users = db::users::list(&wallet.pool).await;
let categories = db::categories::list(&wallet.pool).await;
@@ -82,7 +84,7 @@ pub async fn create(
wallet: &Wallet,
query: queries::Payments,
form: HashMap<String, String>,
-) -> Response<Body> {
+) -> Response<Full<Bytes>> {
let error = |e: &str| {
create_form_feedback(wallet, query, form.clone(), Some(e.to_string()))
};
@@ -125,7 +127,7 @@ pub async fn update_form(
id: i64,
wallet: &Wallet,
query: queries::Payments,
-) -> Response<Body> {
+) -> Response<Full<Bytes>> {
update_form_feedback(id, wallet, query, HashMap::new(), None).await
}
@@ -135,7 +137,7 @@ async fn update_form_feedback(
query: queries::Payments,
form: HashMap<String, String>,
error: Option<String>,
-) -> Response<Body> {
+) -> Response<Full<Bytes>> {
let payment = db::payments::get_for_form(&wallet.pool, id).await;
let users = db::users::list(&wallet.pool).await;
let categories = db::categories::list(&wallet.pool).await;
@@ -164,7 +166,7 @@ pub async fn update(
wallet: &Wallet,
query: queries::Payments,
form: HashMap<String, String>,
-) -> Response<Body> {
+) -> Response<Full<Bytes>> {
let error = |e: &str| {
update_form_feedback(
id,
@@ -207,7 +209,7 @@ pub async fn delete(
id: i64,
wallet: &Wallet,
query: queries::Payments,
-) -> Response<Body> {
+) -> Response<Full<Bytes>> {
if db::payments::delete(&wallet.pool, id).await {
let query = queries::Payments {
highlight: None,
@@ -229,7 +231,7 @@ pub async fn delete(
pub async fn search_category(
wallet: &Wallet,
query: queries::PaymentCategory,
-) -> Response<Body> {
+) -> Response<Full<Bytes>> {
match db::payments::search_category(&wallet.pool, query.payment_name).await
{
Some(category_id) => utils::with_headers(
diff --git a/src/controller/statistics.rs b/src/controller/statistics.rs
index 38a5787..eb1e704 100644
--- a/src/controller/statistics.rs
+++ b/src/controller/statistics.rs
@@ -1,4 +1,6 @@
-use hyper::{Body, Response};
+use http_body_util::Full;
+use hyper::body::Bytes;
+use hyper::Response;
use tera::Context;
use crate::controller::utils;
@@ -6,7 +8,7 @@ use crate::controller::wallet::Wallet;
use crate::db;
use crate::templates;
-pub async fn get(wallet: &Wallet) -> Response<Body> {
+pub async fn get(wallet: &Wallet) -> Response<Full<Bytes>> {
let categories = db::categories::list(&wallet.pool).await;
let payments = db::payments::list_for_stats(&wallet.pool).await;
let incomes = db::incomes::total_each_month(&wallet.pool).await;
diff --git a/src/controller/utils.rs b/src/controller/utils.rs
index fb1d9ad..1b58c68 100644
--- a/src/controller/utils.rs
+++ b/src/controller/utils.rs
@@ -1,18 +1,18 @@
+use http_body_util::Full;
+use hyper::body::Bytes;
use hyper::header::{
HeaderName, HeaderValue, CACHE_CONTROL, CONTENT_TYPE, LOCATION,
};
-use hyper::{Body, Response, StatusCode};
+use hyper::{Response, StatusCode};
use std::collections::HashMap;
use tera::{Context, Tera};
-use tokio::fs::File;
-use tokio_util::codec::{BytesCodec, FramedRead};
use crate::controller::error;
pub fn with_headers(
- response: Response<Body>,
+ response: Response<Full<Bytes>>,
headers: Vec<(HeaderName, &str)>,
-) -> Response<Body> {
+) -> Response<Full<Bytes>> {
let mut response = response;
let response_headers = response.headers_mut();
for (name, value) in headers {
@@ -26,7 +26,7 @@ pub fn template(
templates: &Tera,
path: &str,
context: Context,
-) -> Response<Body> {
+) -> Response<Full<Bytes>> {
let mut context = context;
context.insert("assets", assets);
@@ -47,7 +47,7 @@ fn server_error(
assets: &HashMap<String, String>,
templates: &Tera,
msg: &str,
-) -> Response<Body> {
+) -> Response<Full<Bytes>> {
with_headers(
Response::new(
error::template(assets, templates, "Erreur serveur", msg).into(),
@@ -56,36 +56,34 @@ fn server_error(
)
}
-pub fn text(str: String) -> Response<Body> {
+pub fn text(str: String) -> Response<Full<Bytes>> {
let mut response = Response::new(str.into());
*response.status_mut() = StatusCode::OK;
response
}
-pub fn ok() -> Response<Body> {
+pub fn ok() -> Response<Full<Bytes>> {
let mut response = Response::default();
*response.status_mut() = StatusCode::OK;
response
}
-pub fn redirect(uri: &str) -> Response<Body> {
+pub fn redirect(uri: &str) -> Response<Full<Bytes>> {
let mut response = Response::default();
*response.status_mut() = StatusCode::MOVED_PERMANENTLY;
with_headers(response, vec![(LOCATION, uri), (CACHE_CONTROL, "no-cache")])
}
-pub fn not_found() -> Response<Body> {
+pub fn not_found() -> Response<Full<Bytes>> {
let mut response = Response::default();
*response.status_mut() = StatusCode::NOT_FOUND;
response
}
-pub async fn file(filename: &str, content_type: &str) -> Response<Body> {
- if let Ok(file) = File::open(filename).await {
- let stream = FramedRead::new(file, BytesCodec::new());
- let body = Body::wrap_stream(stream);
+pub async fn file(filename: &str, content_type: &str) -> Response<Full<Bytes>> {
+ if let Ok(contents) = tokio::fs::read(filename).await {
with_headers(
- Response::new(body),
+ Response::new(Full::new(contents.into())),
vec![
(CACHE_CONTROL, "max-age=3153600000"),
(CONTENT_TYPE, content_type),
diff --git a/src/main.rs b/src/main.rs
index 65d52df..5f3b8c6 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,7 +1,8 @@
-use hyper::service::{make_service_fn, service_fn};
-use hyper::Server;
+use hyper::server::conn::http1;
+use hyper::service::service_fn;
+use hyper_util::rt::TokioIo;
use sqlx::sqlite::SqlitePool;
-use std::convert::Infallible;
+use tokio::net::TcpListener;
#[macro_use]
extern crate log;
@@ -23,7 +24,7 @@ mod validation;
use model::config;
#[tokio::main]
-async fn main() {
+async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
env_logger::Builder::from_env(
env_logger::Env::default().default_filter_or("warn"),
)
@@ -42,26 +43,36 @@ async fn main() {
tokio::spawn(jobs::start(config.clone(), pool.clone(), templates.clone()));
- let make_svc = make_service_fn(|_conn| {
+ let listener = TcpListener::bind(config.socket_address).await?;
+ info!("Starting server at {}", config.socket_address);
+
+ loop {
let config = config.clone();
let pool = pool.clone();
let assets = assets.clone();
let templates = templates.clone();
- async move {
- Ok::<_, Infallible>(service_fn(move |req| {
- routes::routes(
- config.clone(),
- pool.clone(),
- assets.clone(),
- templates.clone(),
- req,
- )
- }))
- }
- });
+ let (stream, _) = listener.accept().await?;
- info!("Starting server at {}", config.socket_address);
- if let Err(e) = Server::bind(&config.socket_address).serve(make_svc).await {
- error!("server error: {}", e);
+ let io = TokioIo::new(stream);
+
+ tokio::task::spawn(async move {
+ if let Err(err) = http1::Builder::new()
+ .serve_connection(
+ io,
+ service_fn(move |req| {
+ routes::routes(
+ config.clone(),
+ pool.clone(),
+ assets.clone(),
+ templates.clone(),
+ req,
+ )
+ }),
+ )
+ .await
+ {
+ println!("Error serving connection: {:?}", err);
+ }
+ });
}
}
diff --git a/src/routes.rs b/src/routes.rs
index ef63c8e..ae87d39 100644
--- a/src/routes.rs
+++ b/src/routes.rs
@@ -1,4 +1,6 @@
-use hyper::{Body, Method, Request, Response};
+use http_body_util::{BodyExt, Full};
+use hyper::body::{Bytes, Incoming};
+use hyper::{Method, Request, Response};
use serde::Deserialize;
use sqlx::sqlite::SqlitePool;
use std::collections::HashMap;
@@ -19,8 +21,8 @@ pub async fn routes(
pool: SqlitePool,
assets: HashMap<String, String>,
templates: Tera,
- request: Request<Body>,
-) -> Result<Response<Body>, Infallible> {
+ request: Request<Incoming>,
+) -> Result<Response<Full<Bytes>>, Infallible> {
let method = request.method();
let uri = request.uri();
let path = &uri.path().split('/').collect::<Vec<&str>>()[1..];
@@ -68,7 +70,7 @@ pub async fn routes(
async fn connected_user(
config: &Config,
pool: &SqlitePool,
- request: &Request<Body>,
+ request: &Request<Incoming>,
) -> Option<User> {
let cookie = request.headers().get("COOKIE")?.to_str().ok()?;
let login_token = cookie::extract_token(config, cookie).ok()?;
@@ -78,8 +80,8 @@ async fn connected_user(
async fn authenticated_routes(
config: &Config,
wallet: Wallet,
- request: Request<Body>,
-) -> Response<Body> {
+ request: Request<Incoming>,
+) -> Response<Full<Bytes>> {
let method = request.method();
let uri = request.uri();
let path = &uri.path().split('/').collect::<Vec<&str>>()[1..];
@@ -202,7 +204,7 @@ async fn authenticated_routes(
_ => controller::error::error(
&wallet,
"Page introuvable",
- "La page que recherchez n’existe pas.",
+ "La page que vous recherchez n’existe pas.",
),
}
}
@@ -211,9 +213,12 @@ fn parse_query<'a, T: Deserialize<'a>>(query: Option<&'a str>) -> T {
serde_urlencoded::from_str(query.unwrap_or("")).unwrap()
}
-async fn body_form(request: Request<Body>) -> HashMap<String, String> {
- match hyper::body::to_bytes(request).await {
- Ok(bytes) => form_urlencoded::parse(bytes.as_ref())
+async fn body_form(request: Request<Incoming>) -> HashMap<String, String> {
+ match request.collect().await {
+ // Warning: this is a simplified use case. In principle names can appear multiple times in
+ // a form, and the values should be rolled up into a HashMap<String, Vec<String>>. However
+ // in this example the simpler approach is sufficient.
+ Ok(content) => form_urlencoded::parse(content.to_bytes().as_ref())
.into_owned()
.collect::<HashMap<String, String>>(),
Err(_) => HashMap::new(),