diff --git a/Cargo.lock b/Cargo.lock index df623d8..65b8c9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -578,7 +578,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "fabula_votes_server" -version = "1.2.1" +version = "1.3.0" dependencies = [ "argon2", "blake2", diff --git a/Cargo.toml b/Cargo.toml index 2ec545f..d20f8c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ name = "fabula_votes_server" license = "MPL-2.0" readme = "README.md" authors = ["trotFunky"] -version = "1.2.1" +version = "1.3.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/auth.rs b/src/auth.rs index 7f6b251..e022532 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -185,6 +185,32 @@ pub async fn login(week: u8, form: Form, mut db: Connection, cooki Redirect::to(uri!(week::week(week))) } +#[post("//logout")] +pub async fn logout(week: u8, mut db: Connection, cookies: &CookieJar<'_>) -> Redirect { + let auth_token: Option = match cookies.get_private("auth_token") { + Some(cookie) => Some(cookie.value().to_string()), + None => None + }; + + // Should not be able to log out ? + if auth_token.is_none() { + return Redirect::to(uri!(week::week(week))) + } + + match sqlx::query("DELETE FROM AuthTokens WHERE token == $1;") + .bind(auth_token) + .execute(&mut **db) + .await { + Ok(_) => debug!("Auth token deletion successful"), + Err(error) => debug!("Auth token could not be removed ({error}), proceeding anyway.") + } + + cookies.remove_private("auth_token"); + cookies.remove_private("auth_id"); + + Redirect::to(uri!(week::week(week))) +} + pub fn bypass_auth_debug(cookies: &CookieJar<'_>) { if cookies.get_private("auth_token").is_some() { return diff --git a/src/main.rs b/src/main.rs index 655d9af..ac5bc3f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,13 @@ #[macro_use] extern crate rocket; -use rocket::{Rocket, Build, futures}; use rocket::fs::{FileServer, relative}; -use rocket::http::CookieJar; use rocket::response::Redirect; -use rocket::serde::{Serialize, Deserialize, json::Json}; -use rocket_dyn_templates::{Template, context}; +use rocket_dyn_templates::Template; -use rocket_db_pools::{sqlx, sqlx::Row, Database, Connection}; -use sqlx::Error; +use rocket_db_pools::{sqlx, sqlx::Row, Connection}; mod auth; -use auth::User; mod truth; mod vote; @@ -21,7 +16,6 @@ mod week; mod database; mod database_records; -use database_records::*; use database::Db; #[get("/")] @@ -46,7 +40,7 @@ fn rocket() -> _ { vote::fetch_vote_data, vote::vote, truth::create_truth, truth::edit_truth, week::week, week::update_week, week::set_last_week, week::create_week, - auth::login]) + auth::login, auth::logout]) .attach(database::stage()) .attach(Template::fairing()) } diff --git a/src/vote.rs b/src/vote.rs index 6795167..7bd400f 100644 --- a/src/vote.rs +++ b/src/vote.rs @@ -2,7 +2,6 @@ use std::collections::hash_map::Entry; use std::collections::HashMap; use rocket::fairing::AdHoc; use rocket::form::Form; -use rocket::futures::TryFutureExt; use rocket::http::CookieJar; use rocket::response::Redirect; use rocket::serde::{Serialize, Deserialize}; @@ -91,12 +90,14 @@ pub async fn vote(week: u8, form: Form, #[derive(Serialize, Deserialize)] #[serde(crate = "rocket::serde")] pub struct VoteData { + truth_count: u8, votes: HashMap>, } // TODO: Cache vote count ? Maintain in state ? #[get("//votes", format = "application/json")] -pub async fn fetch_vote_data(week: u8, mut db: Connection) -> Option> { +pub async fn fetch_vote_data(week: u8, mut db: Connection, cookies: &CookieJar<'_>) -> Option> { + let user = auth::get_user(week, &mut db, cookies).await; let raw_votes: Vec = 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 @@ -110,12 +111,25 @@ pub async fn fetch_vote_data(week: u8, mut db: Connection) -> Opti Vec::::new() }); + 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); + let vote_count = raw_votes.iter().fold(0, |count, votes| {count + votes.votes}); // Only show the graph if we have all the votes and this is not the last week. - // FIXME: Make the 42 not hardcoded - if vote_count < 42 || 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) { + if !user.is_admin && (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)) { return None; } @@ -147,7 +161,7 @@ pub async fn fetch_vote_data(week: u8, mut db: Connection) -> Opti next_truth_number = raw_vote.truth_number + 1; } - Some(Json(VoteData{votes: vote_data})) + Some(Json(VoteData{truth_count: truth_count, votes: vote_data})) } // FIXME: diff --git a/static_files/style.css b/static_files/style.css index f6cc018..edc39a7 100644 --- a/static_files/style.css +++ b/static_files/style.css @@ -23,6 +23,12 @@ padding-bottom: 2em; } +.individual_truth > h3 { + text-align: center; + padding-bottom: 4px; + border-bottom: slategray solid 0.15em; +} + .editor { width: calc(100% - 1.25em); /* The width is calculated *inside* the padding, so adjust for it. */ height: 6eM; @@ -77,8 +83,8 @@ hr { border: none; - border-top: 2px solid #9ec5fe; - color: #9ec5fe; + border-top: 2px dotted slategray; + color: slategray; overflow: visible; text-align: center; height: 5px; diff --git a/static_files/vote_chart.js b/static_files/vote_chart.js index 09850d4..f82b977 100644 --- a/static_files/vote_chart.js +++ b/static_files/vote_chart.js @@ -6,18 +6,20 @@ async function main() { return; } - const keys = ["Vérité 1", "Vérité 2", "Vérité 3", "Vérité 4", "Vérité 5", "Vérité 6", "Vérité 7"] + let keys = [] let datasets = [] - try { - const vote_data = (await vote_response.json()).votes; - for (let player in vote_data) { + const vote_data = (await vote_response.json()); + for (let player in vote_data.votes) { datasets.push({ parsing: true, label: player, - data: vote_data[player], + data: vote_data.votes[player], }) } + for (let i = 1; i <= vote_data.truth_count; i++) { + keys.push("Vérité " + i) + } } catch (error) { console.error("Failed to parse vote data : \n\t" + error.message); return; diff --git a/templates/index.html.tera b/templates/index.html.tera index c2f3afa..e10d6f5 100644 --- a/templates/index.html.tera +++ b/templates/index.html.tera @@ -36,11 +36,17 @@ {% set next_arrow_chara = '⟹' %} {% endif %} +{# Remove the form if all votes are locked, to reduce confusion. #} +{% set lock_truth_form = user.votes | length + 1 == truths | length and week_data.is_last_week != true %} +

{{ title }}

{% if user.logged_in == true %} -

Connecté en tant que {{ user.name }}

+ {% else %} {% endif %}
- {% if user.logged_in == true and user.is_admin == false %} + {% if user.logged_in == true and user.is_admin == false and not lock_truth_form %}
{% endif %} @@ -101,7 +107,7 @@ {% endif %} {% endfor %} - {% if user.logged_in == true and user.is_admin == false %} + {% if user.logged_in == true and user.is_admin == false and not lock_truth_form %}