Compare commits

..

2 Commits

Author SHA1 Message Date
8a7c321044 Fixed bug related to new Vote struct, id and submit date can be None
(useful for posting new votes, ie without id and submit date)
2024-10-05 15:09:51 +02:00
98dbcd11f1 Added submit date to votes 2024-10-05 15:08:41 +02:00
5 changed files with 64 additions and 87 deletions

View File

@@ -3,7 +3,7 @@ use argon2::{
Argon2, Argon2,
}; };
use bytes::{Buf, Bytes}; use bytes::{Buf, Bytes};
use chrono::{DateTime, Days, Utc}; use chrono::{DateTime, Days, NaiveTime, Timelike, Utc};
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use daemonize::Daemonize; use daemonize::Daemonize;
use http_body_util::{BodyExt, Full}; use http_body_util::{BodyExt, Full};
@@ -66,9 +66,10 @@ struct Player {
name: String, name: String,
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize, Debug)]
struct Vote { struct Vote {
id: i64, id: Option<i64>,
submit_date: Option<String>,
plus_player_id: i64, plus_player_id: i64,
plus_nickname: String, plus_nickname: String,
plus_reason: String, plus_reason: String,
@@ -100,9 +101,9 @@ async fn service(
req: Request<Incoming>, req: Request<Incoming>,
db: Arc<Mutex<SqlitePool>>, db: Arc<Mutex<SqlitePool>>,
) -> Result<Response<Body>, Error> { ) -> Result<Response<Body>, Error> {
match req.method() { match *req.method() {
&Method::GET => get(req, db).await, Method::GET => get(req, db).await,
&Method::POST => post(req, db).await, Method::POST => post(req, db).await,
_ => Ok(Response::builder() _ => Ok(Response::builder()
.status(StatusCode::IM_A_TEAPOT) .status(StatusCode::IM_A_TEAPOT)
.body(Body::Empty) .body(Body::Empty)
@@ -224,17 +225,13 @@ async fn get_data(
let mut plus_results: HashMap<i64, i64> = HashMap::new(); let mut plus_results: HashMap<i64, i64> = HashMap::new();
let mut minus_results: HashMap<i64, i64> = HashMap::new(); let mut minus_results: HashMap<i64, i64> = HashMap::new();
let _ = ids.iter().for_each(|x| { ids.iter().for_each(|x| {
let plus_id = x.0; let plus_id = x.0;
if !plus_results.contains_key(&plus_id) { plus_results.entry(plus_id).or_insert(0);
plus_results.insert(plus_id, 0);
}
*plus_results.get_mut(&plus_id).unwrap() += 1; *plus_results.get_mut(&plus_id).unwrap() += 1;
let minus_id = x.1; let minus_id = x.1;
if !minus_results.contains_key(&minus_id) { minus_results.entry(minus_id).or_insert(0);
minus_results.insert(minus_id, 0);
}
*minus_results.get_mut(&minus_id).unwrap() += 1; *minus_results.get_mut(&minus_id).unwrap() += 1;
}); });
@@ -260,11 +257,10 @@ async fn get_votes(req: &Request<Incoming>, db: Arc<Mutex<SqlitePool>>) -> Vec<V
let date = match headers.get("Date-to-fetch") { let date = match headers.get("Date-to-fetch") {
Some(date) => { Some(date) => {
let date = date.to_str().unwrap(); let date = date.to_str().unwrap();
let parsed_date = date.parse::<i64>(); if let Ok(parsed_date) = date.parse::<i64>() {
if parsed_date.is_err() { DateTime::from_timestamp_millis(parsed_date)
None
} else { } else {
DateTime::from_timestamp_millis(parsed_date.unwrap()) None
} }
} }
None => Some(DateTime::from(SystemTime::now())), None => Some(DateTime::from(SystemTime::now())),
@@ -273,25 +269,14 @@ async fn get_votes(req: &Request<Incoming>, db: Arc<Mutex<SqlitePool>>) -> Vec<V
return Vec::new(); return Vec::new();
} }
let formatted_date = format!("{}", date.unwrap().format("%d/%m/%Y")); let formatted_date = format!("{}", date.unwrap().format("%d/%m/%Y"));
let items = sqlx::query!( sqlx::query_as!(
Vote,
r#"SELECT * FROM votes WHERE submit_date = ?1 ORDER BY id"#, r#"SELECT * FROM votes WHERE submit_date = ?1 ORDER BY id"#,
formatted_date formatted_date
) )
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.unwrap(); .unwrap()
items
.iter()
.map(|x| Vote {
id: x.id,
plus_player_id: x.plus_player_id,
plus_nickname: x.plus_nickname.clone(),
plus_reason: x.plus_reason.clone(),
minus_player_id: x.minus_player_id,
minus_nickname: x.minus_nickname.clone(),
minus_reason: x.minus_reason.clone(),
})
.collect()
} }
async fn get_admin( async fn get_admin(
@@ -301,7 +286,7 @@ async fn get_admin(
) -> Result<Response<Body>, Error> { ) -> Result<Response<Body>, Error> {
let authorised = is_authorised(req, db.clone(), 3).await; let authorised = is_authorised(req, db.clone(), 3).await;
if !authorised { if !authorised {
return get_page(&req, "/unauthorised", db).await; return get_page(req, "/unauthorised", db).await;
} }
match path { match path {
"/admin" => get_page(req, path, db).await, "/admin" => get_page(req, path, db).await,
@@ -320,38 +305,19 @@ async fn get_admin(
} }
"/admin/players" => { "/admin/players" => {
let pool = db.clone().lock().unwrap().clone(); let pool = db.clone().lock().unwrap().clone();
let players = sqlx::query!(r#"SELECT id, name FROM players"#) let players = sqlx::query_as!(Player, r#"SELECT id, name FROM players"#)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.unwrap(); .unwrap();
let players: Vec<Player> = players
.iter()
.map(|x| Player {
id: x.id,
name: x.name.clone(),
})
.collect();
let stringed = serde_json::to_string(&players).unwrap_or("".to_string()); let stringed = serde_json::to_string(&players).unwrap_or("".to_string());
Ok(Response::builder().body(Body::new(stringed)).unwrap()) Ok(Response::builder().body(Body::new(stringed)).unwrap())
} }
"/admin/votes" => { "/admin/votes" => {
let pool = db.clone().lock().unwrap().clone(); let pool = db.clone().lock().unwrap().clone();
let votes = sqlx::query!(r#"SELECT * FROM votes"#) let votes = sqlx::query_as!(Vote, r#"SELECT * FROM votes"#)
.fetch_all(&pool) .fetch_all(&pool)
.await .await
.unwrap(); .unwrap();
let votes: Vec<Vote> = votes
.iter()
.map(|x| Vote {
id: x.id,
plus_player_id: x.plus_player_id,
plus_nickname: x.plus_nickname.clone(),
plus_reason: x.plus_reason.clone(),
minus_player_id: x.minus_player_id,
minus_nickname: x.minus_nickname.clone(),
minus_reason: x.minus_reason.clone(),
})
.collect();
let stringed = serde_json::to_string(&votes).unwrap_or("".to_string()); let stringed = serde_json::to_string(&votes).unwrap_or("".to_string());
Ok(Response::builder().body(Body::new(stringed)).unwrap()) Ok(Response::builder().body(Body::new(stringed)).unwrap())
} }
@@ -402,7 +368,21 @@ async fn post_vote(
.body(Body::Empty) .body(Body::Empty)
.unwrap()); .unwrap());
} }
ok().await let date: DateTime<Utc> = DateTime::from(SystemTime::now());
let date = date.checked_add_days(Days::new(1)).unwrap();
let date = date
.with_time(NaiveTime::from_hms_opt(0, 0, 0).unwrap())
.unwrap();
Ok(Response::builder()
.header(
SET_COOKIE,
format!(
"hasvoted=true; Expires={}; Secure; SameSite=Strict",
date.to_rfc2822()
),
)
.body(Body::Empty)
.unwrap())
} }
async fn post_player( async fn post_player(
@@ -550,16 +530,21 @@ async fn post_admin(
}, },
"/admin/edit/vote" => match req_json::<Vote>(req).await { "/admin/edit/vote" => match req_json::<Vote>(req).await {
Some(vote) => { Some(vote) => {
if vote.id.is_none() || vote.submit_date.is_none() {
return bad_request().await;
}
let pool = db.clone().lock().unwrap().clone(); let pool = db.clone().lock().unwrap().clone();
let _ = sqlx::query!( let _ = sqlx::query!(
r#"UPDATE votes r#"UPDATE votes
SET plus_player_id = ?1, SET submit_date = ?1,
plus_nickname = ?2, plus_player_id = ?2,
plus_reason = ?3, plus_nickname = ?3,
minus_player_id = ?4, plus_reason = ?4,
minus_nickname = ?5, minus_player_id = ?5,
minus_reason = ?6 minus_nickname = ?6,
WHERE id = ?7"#, minus_reason = ?7
WHERE id = ?8"#,
vote.submit_date,
vote.plus_player_id, vote.plus_player_id,
vote.plus_nickname, vote.plus_nickname,
vote.plus_reason, vote.plus_reason,
@@ -589,7 +574,7 @@ async fn post_admin(
ok().await ok().await
} }
_ => bad_request().await, _ => bad_request().await,
} },
_ => bad_request().await, _ => bad_request().await,
} }
} }
@@ -749,14 +734,11 @@ async fn is_authorised(req: &Request<Incoming>, db: Arc<Mutex<SqlitePool>>, leve
let perm = user.permissions as u8; let perm = user.permissions as u8;
perm >= level perm >= level
} }
_ => match level { _ => matches!(level, 0),
0 => true,
_ => false,
},
} }
} }
fn check_username(username: &String) -> bool { fn check_username(username: &str) -> bool {
if username.len() > 21 { if username.len() > 21 {
return false; return false;
} }
@@ -768,7 +750,7 @@ fn check_username(username: &String) -> bool {
true true
} }
fn check_password(password: &String) -> bool { fn check_password(password: &str) -> bool {
// one symbol, 10 chars min, one capital letter, one number // one symbol, 10 chars min, one capital letter, one number
if password.len() < 10 { if password.len() < 10 {
return false; return false;

View File

@@ -23,7 +23,7 @@
<div> <div>
<h1>Votes</h1> <h1>Votes</h1>
<h3 id="votes_number"></h3> <h3 id="votes_number"></h3>
<p>id, plus_id, plus_nickname, plus_reason, minus_id, minus_nickname, minus_reason</p> <p>id, submit_date, plus_id, plus_nickname, plus_reason, minus_id, minus_nickname, minus_reason</p>
<div id="votes"></div> <div id="votes"></div>
</div> </div>
</body> </body>

View File

@@ -11,7 +11,6 @@
<link rel="stylesheet" href="static/css/index.css"> <link rel="stylesheet" href="static/css/index.css">
</head> </head>
<body> <body>
<a href="/login" class="login" id="login">Se connecter</a>
<div id="app" class="app"> <div id="app" class="app">
<h1 id="app_title">Vote +</h1> <h1 id="app_title">Vote +</h1>
<label for="player_id">Pour qui votes tu?</label> <label for="player_id">Pour qui votes tu?</label>
@@ -37,5 +36,6 @@
<a href="/results">Résultats</a> <a href="/results">Résultats</a>
<a href="/archives">Archives</a> <a href="/archives">Archives</a>
</div> </div>
<a href="/login" class="login" id="login">Se connecter</a>
</body> </body>
</html> </html>

View File

@@ -80,6 +80,8 @@ async function run() {
item.style.display = "flex"; item.style.display = "flex";
let id = document.createElement("p"); let id = document.createElement("p");
id.textContent = vote["id"]; id.textContent = vote["id"];
let submit_date = document.createElement("input");
submit_date.value = vote["submit_date"];
let plus_id = document.createElement("input"); let plus_id = document.createElement("input");
plus_id.type = "number"; plus_id.type = "number";
plus_id.value = vote["plus_player_id"]; plus_id.value = vote["plus_player_id"];
@@ -98,16 +100,17 @@ async function run() {
edit.textContent = "Edit"; edit.textContent = "Edit";
let del = document.createElement("button"); let del = document.createElement("button");
del.textContent = "Delete"; del.textContent = "Delete";
item.append(id,plus_id,plus_nickname,plus_reason,minus_id,minus_nickname,minus_reason, edit, del); item.append(id,submit_date,plus_id,plus_nickname,plus_reason,minus_id,minus_nickname,minus_reason, edit, del);
votesDiv.append(item); votesDiv.append(item);
edit.addEventListener("click", async () => { edit.addEventListener("click", async () => {
await fetch("/admin/edit/vote", {method: "POST", body: JSON.stringify({"id": votes[i]["id"], await fetch("/admin/edit/vote", {method: "POST", body: JSON.stringify({"id": votes[i]["id"],
"plus_player_id": parseInt(plus_id.value), "submit_date": submit_date.value,
"plus_nickname": plus_nickname.value, "plus_player_id": parseInt(plus_id.value),
"plus_reason": plus_reason.value, "plus_nickname": plus_nickname.value,
"minus_player_id": parseInt(minus_id.value), "plus_reason": plus_reason.value,
"minus_nickname": minus_nickname.value, "minus_player_id": parseInt(minus_id.value),
"minus_reason": minus_reason.value})}); "minus_nickname": minus_nickname.value,
"minus_reason": minus_reason.value})});
window.location.reload(); window.location.reload();
}) })
del.addEventListener("click", async () => { del.addEventListener("click", async () => {

View File

@@ -1,4 +1,5 @@
let vote = { let vote = {
submit_date: null,
plus_player_id: null, plus_player_id: null,
plus_nickname: "", plus_nickname: "",
plus_reason: "", plus_reason: "",
@@ -11,7 +12,7 @@ let current_page = 0;
async function main() { async function main() {
if (read_cookie()) { if (read_cookie()) {
showMessage("Merci pour ton vote!", "Ton vote a bien été prit en compte.", false, "info"); showMessage("Merci pour ton vote!", "Ton vote a bien été pris en compte.", false, "info");
return; return;
} }
let players = await fetch("/data/players").then(r => r.json()); let players = await fetch("/data/players").then(r => r.json());
@@ -95,7 +96,6 @@ async function main() {
method: "post", body: JSON.stringify(vote) method: "post", body: JSON.stringify(vote)
}) })
.then(r => r.status) === 200) { .then(r => r.status) === 200) {
set_cookie();
showMessage("Merci pour ton vote!", "Ton vote a bien été pris en compte.", false, "info"); showMessage("Merci pour ton vote!", "Ton vote a bien été pris en compte.", false, "info");
} }
console.log(vote); console.log(vote);
@@ -160,14 +160,6 @@ function showMessage(title, description, canBeDismissed, type) {
}) })
} }
function set_cookie() {
let date = new Date(Date.now());
date.setDate(date.getDate() + 1);
date.setHours(0, 0,0);
console.log(date);
document.cookie = `hasvoted=true; expires=${date.toUTCString()}; path=/`;
}
function read_cookie() { function read_cookie() {
return document.cookie.includes("hasvoted=true"); return document.cookie.includes("hasvoted=true");
} }