2024-07-23 21:51:42 +01:00
|
|
|
use std::collections::hash_map::Entry;
|
|
|
|
use std::collections::HashMap;
|
|
|
|
use rocket::fairing::AdHoc;
|
|
|
|
use rocket::form::Form;
|
|
|
|
use rocket::http::CookieJar;
|
|
|
|
use rocket::response::Redirect;
|
|
|
|
use rocket::serde::{Serialize, Deserialize};
|
|
|
|
use rocket::serde::json::Json;
|
|
|
|
|
|
|
|
use rocket_db_pools::{sqlx, Connection};
|
|
|
|
|
2024-07-26 01:08:30 +01:00
|
|
|
use crate::{auth, database, week};
|
2024-07-23 21:51:42 +01:00
|
|
|
use crate::database_records::{Vote, VotingData};
|
|
|
|
|
|
|
|
#[derive(FromForm)]
|
|
|
|
pub struct VoteForm {
|
|
|
|
truth_votes: HashMap<u32, u16>
|
|
|
|
}
|
|
|
|
|
|
|
|
#[post("/<week>/vote", data="<form>")]
|
|
|
|
pub async fn vote(week: u8, form: Form<VoteForm>,
|
|
|
|
mut db: Connection<database::Db>, cookies: &CookieJar<'_>) -> Redirect {
|
|
|
|
let user = auth::get_user(week, &mut db, cookies).await;
|
|
|
|
|
|
|
|
if !user.logged_in {
|
|
|
|
cookies.add(("toast_error", "Vous n'avez pas la permission de changer de vote."));
|
2024-07-26 01:08:30 +01:00
|
|
|
return Redirect::to(uri!(week::week(week)));
|
2024-07-23 21:51:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
let filtered_votes = form.truth_votes.iter().filter_map(
|
|
|
|
|vote| {
|
|
|
|
if *vote.1 != 0 {
|
|
|
|
Some(vote)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
let mut had_error = false;
|
|
|
|
for (truth_id, voted_id) in filtered_votes {
|
|
|
|
match user.votes.iter().find(|vote: &&Vote| {vote.truth_id == *truth_id}) {
|
|
|
|
Some(vote) => {
|
|
|
|
if *voted_id == vote.voted_id {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
debug!("Player {:?} updating vote {:?} for truth {truth_id} : \n\t\
|
|
|
|
Previously voted {:?}, now voted {voted_id}", user.id, vote.id, vote.voted_id);
|
|
|
|
match sqlx::query("UPDATE Votes SET voted_id = $3 WHERE truth_id == $1 AND voter_id == $2;")
|
|
|
|
.bind(truth_id)
|
|
|
|
.bind(user.id)
|
|
|
|
.bind(voted_id)
|
|
|
|
.fetch_optional(&mut **db)
|
|
|
|
.await {
|
|
|
|
Ok(_) => {}
|
|
|
|
Err(error) => {
|
|
|
|
error!("Error while submitting a vote : {error}");
|
|
|
|
had_error = true;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
None => {
|
|
|
|
debug!("Player {:?} voting {voted_id} for truth {truth_id}", user.id);
|
2024-07-26 00:45:53 +01:00
|
|
|
// TODO: Find a way to use only one statement ? --> query_many
|
2024-07-23 21:51:42 +01:00
|
|
|
// Cannot all launch and await because all connect to DB
|
|
|
|
match sqlx::query("INSERT INTO Votes (truth_id, voter_id, voted_id) VALUES ($1, $2, $3);")
|
|
|
|
.bind(truth_id)
|
|
|
|
.bind(user.id)
|
|
|
|
.bind(voted_id)
|
|
|
|
.fetch_optional(&mut **db)
|
|
|
|
.await {
|
|
|
|
Ok(_) => {}
|
|
|
|
Err(error) => {
|
|
|
|
error!("Error while submitting a vote : {error}");
|
|
|
|
had_error = true;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if had_error {
|
|
|
|
cookies.add(("toast_error", "Il y a eu un problème lors de la soumission du vote !"));
|
|
|
|
} else {
|
|
|
|
debug!("Vote successful")
|
|
|
|
}
|
|
|
|
|
2024-07-26 01:08:30 +01:00
|
|
|
Redirect::to(uri!(week::week(week)))
|
2024-07-23 21:51:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize, Deserialize)]
|
|
|
|
#[serde(crate = "rocket::serde")]
|
|
|
|
pub struct VoteData {
|
2024-07-26 19:25:41 +01:00
|
|
|
truth_count: u8,
|
2024-07-23 21:51:42 +01:00
|
|
|
votes: HashMap<String, Vec<u8>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Cache vote count ? Maintain in state ?
|
|
|
|
#[get("/<week>/votes", format = "application/json")]
|
|
|
|
pub async fn fetch_vote_data(week: u8, mut db: Connection<database::Db>) -> Option<Json<VoteData>> {
|
|
|
|
let raw_votes: Vec<VotingData> = sqlx::query_as("
|
|
|
|
SELECT Players.name as votes_for, Truths.number as truth_number, count(*) as votes FROM Votes
|
|
|
|
JOIN Players ON Votes.voted_id == Players.id
|
|
|
|
JOIN Truths on Votes.truth_id == Truths.id AND Truths.week == $1
|
|
|
|
GROUP BY votes_for, truth_number
|
|
|
|
ORDER BY votes_for, truth_number;")
|
|
|
|
.bind(week)
|
|
|
|
.fetch_all(&mut **db)
|
|
|
|
.await.unwrap_or_else(|error| {
|
|
|
|
error!("Error while fetching vote data {error}");
|
|
|
|
Vec::<VotingData>::new()
|
|
|
|
});
|
|
|
|
|
2024-07-26 19:25:41 +01:00
|
|
|
let truth_count: u8 = sqlx::query_scalar("SELECT count(id) from Truths WHERE week == $1;")
|
|
|
|
.bind(week)
|
|
|
|
.fetch_one(&mut **db)
|
|
|
|
.await.unwrap_or(0);
|
|
|
|
|
|
|
|
let player_count: u8 = sqlx::query_scalar("SELECT count(id) from Players WHERE is_admin == 0;")
|
|
|
|
.fetch_one(&mut **db)
|
|
|
|
.await.unwrap_or(0);
|
|
|
|
|
|
|
|
// Each player should have a truth assigned to them which they cannot vote on.
|
|
|
|
let max_vote_count = truth_count * (player_count - 1);
|
|
|
|
|
2024-07-23 21:51:42 +01:00
|
|
|
let vote_count = raw_votes.iter().fold(0, |count, votes| {count + votes.votes});
|
2024-07-26 00:45:53 +01:00
|
|
|
// Only show the graph if we have all the votes and this is not the last week.
|
2024-07-26 19:25:41 +01:00
|
|
|
if max_vote_count == 0
|
|
|
|
|| vote_count < max_vote_count
|
|
|
|
|| week == sqlx::query_scalar("SELECT number from Weeks WHERE is_last_week == 1;")
|
|
|
|
.fetch_optional(&mut **db)
|
|
|
|
.await.unwrap_or(Some(0)).unwrap_or(0) {
|
2024-07-23 21:51:42 +01:00
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-07-24 15:23:52 +01:00
|
|
|
// The hash map makes storing and retrieving the data really easy, *but*
|
|
|
|
// it does lose the order of the original array (which is sorted via the SQL).
|
2024-07-23 21:51:42 +01:00
|
|
|
let mut vote_data = HashMap::<String, Vec<u8>>::new();
|
|
|
|
let mut next_truth_number = 1;
|
|
|
|
for raw_vote in raw_votes {
|
|
|
|
// The truth_number is monotonous (sorted by the SQL request).
|
|
|
|
// If the next vote's truth is lower, we have changed player.
|
|
|
|
if next_truth_number > raw_vote.truth_number {
|
|
|
|
next_truth_number = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
let votes_for_player: &mut Vec<u8> = match vote_data.entry(raw_vote.votes_for) {
|
|
|
|
Entry::Occupied(existing) => {existing.into_mut()}
|
2024-07-23 21:47:18 +01:00
|
|
|
Entry::Vacant(vacant) => {
|
|
|
|
next_truth_number = 1; // We changed user, reset to 1.
|
|
|
|
vacant.insert(Vec::<u8>::new())}
|
2024-07-23 21:51:42 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
// Fill up missing spaces if we are missing any.
|
|
|
|
for _ in next_truth_number..raw_vote.truth_number {
|
|
|
|
votes_for_player.push(0);
|
|
|
|
}
|
|
|
|
// Update with the vote result.
|
|
|
|
votes_for_player.push(raw_vote.votes);
|
|
|
|
next_truth_number = raw_vote.truth_number + 1;
|
|
|
|
}
|
|
|
|
|
2024-07-26 19:25:41 +01:00
|
|
|
Some(Json(VoteData{truth_count: truth_count, votes: vote_data}))
|
2024-07-23 21:51:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// FIXME:
|
|
|
|
pub fn stage() -> AdHoc {
|
|
|
|
AdHoc::on_ignite("SQLx Stage", |rocket| async {
|
|
|
|
rocket.mount("/", routes![vote, fetch_vote_data])
|
|
|
|
})
|
|
|
|
}
|