Compare commits

..

5 commits

Author SHA1 Message Date
0c162d3b42 vote: Always display graph for the admin 2024-07-26 23:49:23 +01:00
c4e252dbc2 auth: Add a logout button
Add a logout button that clears the auth cookies, logging out the user.
It also tries to remove the auth token from the databse, but will ignore
any error during the database operation.

Do some include clean-ups as well.
2024-07-26 20:32:16 +01:00
36be6b51ae style: Update truth titles, change style of hr
Center the truth titles and add a border with the body, padding accordingly.
To no overload the page and maintain connection, make the hr dotted and match the color.
2024-07-26 19:52:02 +01:00
07d8cf42d7 vote: Properly take player and truth numbers into account
Fetch the amount of truths for the week and the player count to find the total possible votes.
Use this to remove the hardocded value in the check.
Update the check to fail if there was an error as well.

Now that the truth count is calculated, send it to the javascript to build the graph
labels automatically as well.
2024-07-26 19:25:41 +01:00
ba98c3be84 index: Remove the form if all votes were cast
Currently, if all votes are cast and the week is locked the selections are disabled but
the form and button still exist and might cause confusion.
Remove them if all votes are cast and we are not on the last week anymore.
2024-07-26 17:57:06 +01:00
8 changed files with 76 additions and 28 deletions

2
Cargo.lock generated
View file

@ -578,7 +578,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0"
[[package]] [[package]]
name = "fabula_votes_server" name = "fabula_votes_server"
version = "1.2.1" version = "1.3.0"
dependencies = [ dependencies = [
"argon2", "argon2",
"blake2", "blake2",

View file

@ -3,7 +3,7 @@ name = "fabula_votes_server"
license = "MPL-2.0" license = "MPL-2.0"
readme = "README.md" readme = "README.md"
authors = ["trotFunky"] authors = ["trotFunky"]
version = "1.2.1" version = "1.3.0"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View file

@ -185,6 +185,32 @@ pub async fn login(week: u8, form: Form<AuthForm>, mut db: Connection<Db>, cooki
Redirect::to(uri!(week::week(week))) Redirect::to(uri!(week::week(week)))
} }
#[post("/<week>/logout")]
pub async fn logout(week: u8, mut db: Connection<Db>, cookies: &CookieJar<'_>) -> Redirect {
let auth_token: Option<String> = 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<'_>) { pub fn bypass_auth_debug(cookies: &CookieJar<'_>) {
if cookies.get_private("auth_token").is_some() { if cookies.get_private("auth_token").is_some() {
return return

View file

@ -1,18 +1,13 @@
#[macro_use] extern crate rocket; #[macro_use] extern crate rocket;
use rocket::{Rocket, Build, futures};
use rocket::fs::{FileServer, relative}; use rocket::fs::{FileServer, relative};
use rocket::http::CookieJar;
use rocket::response::Redirect; 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 rocket_db_pools::{sqlx, sqlx::Row, Connection};
use sqlx::Error;
mod auth; mod auth;
use auth::User;
mod truth; mod truth;
mod vote; mod vote;
@ -21,7 +16,6 @@ mod week;
mod database; mod database;
mod database_records; mod database_records;
use database_records::*;
use database::Db; use database::Db;
#[get("/")] #[get("/")]
@ -46,7 +40,7 @@ fn rocket() -> _ {
vote::fetch_vote_data, vote::vote, vote::fetch_vote_data, vote::vote,
truth::create_truth, truth::edit_truth, truth::create_truth, truth::edit_truth,
week::week, week::update_week, week::set_last_week, week::create_week, week::week, week::update_week, week::set_last_week, week::create_week,
auth::login]) auth::login, auth::logout])
.attach(database::stage()) .attach(database::stage())
.attach(Template::fairing()) .attach(Template::fairing())
} }

View file

@ -2,7 +2,6 @@ use std::collections::hash_map::Entry;
use std::collections::HashMap; use std::collections::HashMap;
use rocket::fairing::AdHoc; use rocket::fairing::AdHoc;
use rocket::form::Form; use rocket::form::Form;
use rocket::futures::TryFutureExt;
use rocket::http::CookieJar; use rocket::http::CookieJar;
use rocket::response::Redirect; use rocket::response::Redirect;
use rocket::serde::{Serialize, Deserialize}; use rocket::serde::{Serialize, Deserialize};
@ -91,12 +90,14 @@ pub async fn vote(week: u8, form: Form<VoteForm>,
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
#[serde(crate = "rocket::serde")] #[serde(crate = "rocket::serde")]
pub struct VoteData { pub struct VoteData {
truth_count: u8,
votes: HashMap<String, Vec<u8>>, votes: HashMap<String, Vec<u8>>,
} }
// TODO: Cache vote count ? Maintain in state ? // TODO: Cache vote count ? Maintain in state ?
#[get("/<week>/votes", format = "application/json")] #[get("/<week>/votes", format = "application/json")]
pub async fn fetch_vote_data(week: u8, mut db: Connection<database::Db>) -> Option<Json<VoteData>> { pub async fn fetch_vote_data(week: u8, mut db: Connection<database::Db>, cookies: &CookieJar<'_>) -> Option<Json<VoteData>> {
let user = auth::get_user(week, &mut db, cookies).await;
let raw_votes: Vec<VotingData> = sqlx::query_as(" let raw_votes: Vec<VotingData> = sqlx::query_as("
SELECT Players.name as votes_for, Truths.number as truth_number, count(*) as votes FROM Votes 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 Players ON Votes.voted_id == Players.id
@ -110,12 +111,25 @@ pub async fn fetch_vote_data(week: u8, mut db: Connection<database::Db>) -> Opti
Vec::<VotingData>::new() Vec::<VotingData>::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}); 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. // Only show the graph if we have all the votes and this is not the last week.
// FIXME: Make the 42 not hardcoded if !user.is_admin && (max_vote_count == 0
if vote_count < 42 || week == sqlx::query_scalar("SELECT number from Weeks WHERE is_last_week == 1;") || vote_count < max_vote_count
.fetch_optional(&mut **db) || week == sqlx::query_scalar("SELECT number from Weeks WHERE is_last_week == 1;")
.await.unwrap_or(Some(0)).unwrap_or(0) { .fetch_optional(&mut **db)
.await.unwrap_or(Some(0)).unwrap_or(0)) {
return None; return None;
} }
@ -147,7 +161,7 @@ pub async fn fetch_vote_data(week: u8, mut db: Connection<database::Db>) -> Opti
next_truth_number = raw_vote.truth_number + 1; 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: // FIXME:

View file

@ -23,6 +23,12 @@
padding-bottom: 2em; padding-bottom: 2em;
} }
.individual_truth > h3 {
text-align: center;
padding-bottom: 4px;
border-bottom: slategray solid 0.15em;
}
.editor { .editor {
width: calc(100% - 1.25em); /* The width is calculated *inside* the padding, so adjust for it. */ width: calc(100% - 1.25em); /* The width is calculated *inside* the padding, so adjust for it. */
height: 6eM; height: 6eM;
@ -77,8 +83,8 @@
hr { hr {
border: none; border: none;
border-top: 2px solid #9ec5fe; border-top: 2px dotted slategray;
color: #9ec5fe; color: slategray;
overflow: visible; overflow: visible;
text-align: center; text-align: center;
height: 5px; height: 5px;

View file

@ -6,18 +6,20 @@ async function main() {
return; 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 = [] let datasets = []
try { try {
const vote_data = (await vote_response.json()).votes; const vote_data = (await vote_response.json());
for (let player in vote_data) { for (let player in vote_data.votes) {
datasets.push({ datasets.push({
parsing: true, parsing: true,
label: player, 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) { } catch (error) {
console.error("Failed to parse vote data : \n\t" + error.message); console.error("Failed to parse vote data : \n\t" + error.message);
return; return;

View file

@ -36,11 +36,17 @@
{% set next_arrow_chara = '⟹' %} {% set next_arrow_chara = '⟹' %}
{% endif %} {% 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 %}
<body> <body>
<div class="top_bar"> <div class="top_bar">
<h1>{{ title }}</h1> <h1>{{ title }}</h1>
{% if user.logged_in == true %} {% if user.logged_in == true %}
<p>Connecté en tant que <b>{{ user.name }}</b></p> <form class="login" id="logout" action="/{{ week_data.number }}/logout" method="POST">
Connecté en tant que <b>{{ user.name }}</b>
<button form="logout">Déconnecter</button>
</form>
{% else %} {% else %}
<form class="login" id="login" action="/{{ week_data.number }}/login" method="POST"> <form class="login" id="login" action="/{{ week_data.number }}/login" method="POST">
<label>Pseudo <input form="login" type="text" name="name"/></label> <label>Pseudo <input form="login" type="text" name="name"/></label>
@ -76,7 +82,7 @@
</form> </form>
{% endif %} {% endif %}
</div> </div>
{% 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 %}
<form id="truths" action="/{{ week_data.number }}/vote" method="POST"> <form id="truths" action="/{{ week_data.number }}/vote" method="POST">
{% endif %} {% endif %}
@ -101,7 +107,7 @@
{% endif %} {% endif %}
{% endfor %} {% 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 %}
<br/> <br/>
<button form="truths"> <button form="truths">
{%- if user.logged_in == true and user.has_week_vote == true -%} {%- if user.logged_in == true and user.has_week_vote == true -%}