diff --git a/README.md b/README.md index 2b6d940..cb8a569 100644 --- a/README.md +++ b/README.md @@ -7,17 +7,15 @@ vote and who we think wrote them and see some stats ! A list of things that could be implemented/added to the application, some of them are needed for "feature completeness" ! - - [x] Being able to change from one week to the next - - [x] Create new weeks for the admin - - [x] Proper week redirection - - [ ] Correctly handle non-existing week number + - [ ] Being able to change from one week to the next + - [ ] Create new weeks for the admin + - [ ] Proper week redirection - [x] Add introduction to the weekly truths - [ ] Bundle static assets in the binary - [ ] Move the database queries to their own functions - [ ] Cache those results - [ ] Centralize Markdown parsing ? - [ ] Use fairings for the different elements ? - - [ ] Use guards for User calls ? # Dependencies diff --git a/src/auth.rs b/src/auth.rs index 7f6b251..ac909f0 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -9,7 +9,7 @@ use argon2::{Argon2, PasswordHash, PasswordVerifier}; use blake2::{Blake2b512, Digest}; use blake2::digest::FixedOutput; use crate::database_records::{AuthTokens, PlayerLoginInfo, Vote}; -use crate::{database, week}; +use crate::database; use database::Db; // TODO: Make FromRequest guard https://api.rocket.rs/v0.5/rocket/request/trait.FromRequest and split admin @@ -86,8 +86,7 @@ pub async fn get_user(week: u8, db: &mut Connection, cookies: &CookieJar<'_> (String::new(), false) }; - // TODO: Move to src/vote.rs - let votes: Vec = if logged_in && !is_admin { + let votes: Vec = if logged_in { sqlx::query_as("SELECT Votes.* FROM Votes JOIN Truths ON Votes.truth_id == Truths.id AND Truths.week == $1 WHERE voter_id == $2 ORDER BY Truths.number;") .bind(week) .bind(&id_str) @@ -126,8 +125,8 @@ pub struct AuthForm { password: String } -#[post("//login", data="
")] -pub async fn login(week: u8, form: Form, mut db: Connection, cookies: &CookieJar<'_>) -> Redirect { +#[post("/login", data="")] +pub async fn login(form: Form, mut db: Connection, cookies: &CookieJar<'_>) -> Redirect { let user_search: Result = sqlx::query_as("SELECT id, is_admin, name, pwd_hash FROM Players WHERE name == $1") .bind(&form.name) .fetch_one(&mut **db) @@ -136,7 +135,7 @@ pub async fn login(week: u8, form: Form, mut db: Connection, cooki if user_search.is_err() { error!("Login failed : invalid user {:?}, err: {:?}", form.name, user_search.err()); cookies.add(("toast_error", "Impossible de se connecter !")); - return Redirect::to(uri!(week::week(week))); + return Redirect::to(uri!("/")); } let new_user = user_search.unwrap(); @@ -144,7 +143,7 @@ pub async fn login(week: u8, form: Form, mut db: Connection, cooki if password_hash_parse.is_err() { error!("Login failed : could not parse password hash {:?}", password_hash_parse.err()); cookies.add(("toast_error", "Impossible de se connecter !")); - return Redirect::to(uri!(week::week(week))); + return Redirect::to(uri!("/")); } let password_hash = password_hash_parse.unwrap(); @@ -168,7 +167,7 @@ pub async fn login(week: u8, form: Form, mut db: Connection, cooki Err(error) => { error!("Login failed : coult not store auth token in database : {error}"); cookies.add(("toast_error", "Impossible de se connecter !")); - return Redirect::to(uri!(week::week(week))); + return Redirect::to(uri!("/")); } } @@ -178,11 +177,11 @@ pub async fn login(week: u8, form: Form, mut db: Connection, cooki Err(err) => { error!("Login failed : invalid password for {:?}\nError : {err}", new_user.name); cookies.add(("toast_error", "Impossible de se connecter !")); - return Redirect::to(uri!(week::week(week))); + return Redirect::to(uri!("/")); } } - Redirect::to(uri!(week::week(week))) + Redirect::to(uri!("/")) } pub fn bypass_auth_debug(cookies: &CookieJar<'_>) { diff --git a/src/main.rs b/src/main.rs index 655d9af..da44b02 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,8 +12,6 @@ use rocket_db_pools::{sqlx, sqlx::Row, Database, Connection}; use sqlx::Error; mod auth; -use auth::User; - mod truth; mod vote; mod week; @@ -21,8 +19,9 @@ mod week; mod database; mod database_records; -use database_records::*; use database::Db; +use database_records::*; +use auth::User; #[get("/")] async fn index(mut db: Connection) -> Redirect { @@ -42,11 +41,7 @@ async fn index(mut db: Connection) -> Redirect { fn rocket() -> _ { rocket::build() .mount("/", FileServer::from(relative!("static_files"))) - .mount("/", routes![index, - 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]) + .mount("/", routes![index, vote::fetch_vote_data, vote::vote, truth::create_truth, truth::edit_truth, week::week, week::update_week, auth::login]) .attach(database::stage()) .attach(Template::fairing()) } diff --git a/src/truth.rs b/src/truth.rs index 861449c..dd4c0ea 100644 --- a/src/truth.rs +++ b/src/truth.rs @@ -6,7 +6,7 @@ use rocket_db_pools::{sqlx, Connection}; use pulldown_cmark::{Parser, Options}; use sqlx::Row; -use crate::{auth, database, week}; +use crate::{auth, database}; #[derive(FromForm)] pub struct TruthUpdateForm { @@ -20,7 +20,7 @@ pub async fn edit_truth(week: u8, truth_number: u8, form: Form, let user = auth::get_user(week, &mut db, cookies).await; if !user.is_admin { cookies.add(("toast_error", "Vous n'avez pas la permission de changer la vérité.")); - return Redirect::to(uri!(week::week(week))); + return Redirect::to(uri!("/")); } let mut options = Options::empty(); @@ -50,16 +50,16 @@ pub async fn edit_truth(week: u8, truth_number: u8, form: Form, } }; - Redirect::to(uri!(week::week(week))) + Redirect::to(uri!("/")) } -#[post("//new_truth", data="")] +#[post("//new", data="")] pub async fn create_truth(week: u8, form: Form, mut db: Connection, cookies: &CookieJar<'_>) -> Redirect { let user = auth::get_user(week, &mut db, cookies).await; if !user.is_admin { cookies.add(("toast_error", "Vous n'avez pas la permission d'ajouter de vérité.")); - return Redirect::to(uri!(week::week(week))); + return Redirect::to(uri!("/")); } let truth_number: u8 = match sqlx::query("SELECT max(number) from Truths WHERE week == $1;") @@ -76,7 +76,7 @@ pub async fn create_truth(week: u8, form: Form, if truth_number == 0 { error!("Error while getting max truth number."); cookies.add(("toast_error", "Erreur lors de l'ajout de la vérité...")); - return Redirect::to(uri!(week::week(week))); + return Redirect::to(uri!("/")); } let mut options = Options::empty(); @@ -108,5 +108,5 @@ pub async fn create_truth(week: u8, form: Form, debug!("Truth was successfully added"); - Redirect::to(uri!(week::week(week))) + Redirect::to(uri!("/")) } diff --git a/src/vote.rs b/src/vote.rs index f3becde..09239f6 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}; @@ -10,7 +9,7 @@ use rocket::serde::json::Json; use rocket_db_pools::{sqlx, Connection}; -use crate::{auth, database, week}; +use crate::{auth, database}; use crate::database_records::{Vote, VotingData}; #[derive(FromForm)] @@ -25,7 +24,7 @@ pub async fn vote(week: u8, form: Form, if !user.logged_in { cookies.add(("toast_error", "Vous n'avez pas la permission de changer de vote.")); - return Redirect::to(uri!(week::week(week))); + return Redirect::to(uri!("/")); } let filtered_votes = form.truth_votes.iter().filter_map( @@ -62,7 +61,7 @@ pub async fn vote(week: u8, form: Form, } None => { debug!("Player {:?} voting {voted_id} for truth {truth_id}", user.id); - // TODO: Find a way to use only one statement ? --> query_many + // TODO: Find a way to use only one statement ? // 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) @@ -85,7 +84,7 @@ pub async fn vote(week: u8, form: Form, debug!("Vote successful") } - Redirect::to(uri!(week::week(week))) + Redirect::to(uri!("/")) } #[derive(Serialize, Deserialize)] @@ -111,18 +110,11 @@ pub async fn fetch_vote_data(week: u8, mut db: Connection) -> Opti }); 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 number == $1 AND is_last_week == 1;") - .bind(week) - .fetch_optional(&mut **db) - .await.unwrap_or(Some(0)).unwrap_or(0) { + if vote_count < 17 { return None; } - // 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). let mut vote_data = HashMap::>::new(); let mut next_truth_number = 1; for raw_vote in raw_votes { diff --git a/src/week.rs b/src/week.rs index 1eb52db..cbc2e9e 100644 --- a/src/week.rs +++ b/src/week.rs @@ -6,7 +6,6 @@ use rocket::response::Redirect; use rocket_db_pools::{sqlx, Connection}; use rocket_dyn_templates::{context, Template}; -use sqlx::{Acquire, Executor}; use crate::auth; use crate::auth::User; use crate::database::Db; @@ -22,7 +21,7 @@ pub async fn week(week_number: u8, mut db: Connection, cookies: &CookieJar<' .fetch_all(&mut **db).await { Ok(v) => v, Err(error) => { - error!("Some error while getting players : {error}"); + println!("Some error while getting players : {error}"); Vec::::new() } } @@ -85,7 +84,7 @@ pub async fn update_week(week: u8, raw_intro: Form, let user = auth::get_user(week, &mut db, cookies).await; if !user.is_admin { cookies.add(("toast_error", "Vous n'avez pas la permission de changer la semaine.")); - return Redirect::to(uri!(week(week))); + return Redirect::to(uri!("/")); } let mut options = Options::empty(); @@ -113,100 +112,5 @@ pub async fn update_week(week: u8, raw_intro: Form, } }; - Redirect::to(uri!(week(week))) -} - -#[post("//set_last")] -pub async fn set_last_week(week: u8, mut db: Connection, cookies: &CookieJar<'_>) -> Redirect { - let user = auth::get_user(week, &mut db, cookies).await; - if !user.is_admin { - cookies.add(("toast_error", "Vous n'avez pas la permission de changer la semaine.")); - return Redirect::to(uri!(week(week))); - } - - let add_error_cookie = || - cookies.add(("toast_error", "Erreur lors du changement d'état de la semaine.")); - - let db_transaction_connection = db.begin().await; - if db_transaction_connection.is_err() { - error!("Could not start database transaction for last week change : {:?}", db_transaction_connection.unwrap_err()); - add_error_cookie(); - return Redirect::to(uri!(week(week))); - } - let mut db_transaction = db_transaction_connection.unwrap(); - - // Remove the last flag from other weeks, if set. - match db_transaction.execute("UPDATE Weeks SET is_last_week = 0 WHERE is_last_week == 1;") - .await { - Ok(_) => debug!("Succesfully cleared is_last_week"), - Err(error) => { - error!("Failed to clear last week status : {error}"); - add_error_cookie(); - return Redirect::to(uri!(week(week))); - } - }; - - // We should set one week, if not there's something wrong : rollback. - if match sqlx::query("UPDATE Weeks SET is_last_week = 1 WHERE number == $1;") - .bind(week) - .execute(&mut *db_transaction) - .await { - Ok(result) => result.rows_affected(), - Err(error) => { - error!("Error while setting last week status : {error}"); - 0 - } - } == 1 { - db_transaction.commit().await.unwrap_or_else(|error| { - error!("Error while committing week is last transaction : {error}"); - add_error_cookie(); - }) - } else { - db_transaction.rollback().await.unwrap_or_else(|error| { - error!("Error while rolling back week is last transaction : {error}"); - add_error_cookie(); - }) - } - - Redirect::to(uri!(week(week))) -} - -#[get("//create")] -pub async fn create_week(week: u8, mut db: Connection, cookies: &CookieJar<'_>) -> Redirect { - let user = auth::get_user(week, &mut db, cookies).await; - if !user.is_admin { - cookies.add(("toast_error", "Vous n'avez pas la permission de changer la semaine.")); - return Redirect::to(uri!(week(week - 1))); - } - - let week_check: Result, sqlx::Error> = sqlx::query_scalar("SELECT number from Weeks WHERE number == $1;") - .bind(week) - .fetch_optional(&mut **db) - .await; - - if week_check.is_err() { - error!("Error while checking week existence : {:?}", week_check.unwrap_err()); - cookies.add(("toast_error", "Erreur en vérifiant que la semaine n'existe pas déjà")); - return Redirect::to(uri!(week(week - 1))); - } - - if week_check.unwrap().is_some() { - debug!("Week {week} already exists, not creating."); - return Redirect::to(uri!(week(week))); - } - - match sqlx::query("INSERT INTO Weeks (number, is_last_week, rendered_text, raw_text) VALUES ($1, 0, \"\", \"\");") - .bind(week) - .execute(&mut **db) - .await { - Ok(_) => { - debug!("Succesfully created new week {week}"); - Redirect::to(uri!(week(week))) - }, - Err(error) => { - error!("Error while creating new week {week} : {error}"); - cookies.add(("toast_error", "Erreur en créant la nouvelle selmaine.")); - Redirect::to(uri!(week(week - 1))) - } - } + Redirect::to(uri!("/")) } diff --git a/static_files/style.css b/static_files/style.css index f6cc018..3de7065 100644 --- a/static_files/style.css +++ b/static_files/style.css @@ -24,7 +24,7 @@ } .editor { - width: calc(100% - 1.25em); /* The width is calculated *inside* the padding, so adjust for it. */ + width: 100%; height: 6eM; font-size: large; padding: 0.5em; @@ -47,34 +47,6 @@ top: 25%; } -.week_change:link, .week_change:visited { - color: black; - text-decoration: none; - transition: all .2s ease-in-out; -} - -.week_change:hover, .week_change:focus { - color: mediumpurple; - text-shadow: 0 2px 2px slategray; -} - -.week_change_hidden { - padding-left: 0.5eM; - padding-right: 0.5eM; -} - -@media (orientation: portrait) { - .truth_list { - width: 60vw; - } - - .graph { - width: 35vw; - } -} - -/* Global styling */ - hr { border: none; border-top: 2px solid #9ec5fe; @@ -102,3 +74,13 @@ body { body > h2 { padding-left: 0.25eM; } + +@media (orientation: portrait) { + .truth_list { + width: 60vw; + } + + .graph { + width: 35vw; + } +} diff --git a/static_files/vote_chart.js b/static_files/vote_chart.js index 09850d4..7bdae87 100644 --- a/static_files/vote_chart.js +++ b/static_files/vote_chart.js @@ -1,5 +1,14 @@ -const limit_ratio = 1.3 -const colors = ['#b6b8fc', '#b6f4fc', '#fcb6cc', '#e0fcb6', '#fcdcb6', '#b6fcc8', '#f0b6fc'] + +// const names = ["Bystus", "Dory", "Fen", "Lucky", "Nico", "Peran", "trot"] +// +// +// let data = []; +// for (let i = 0; i < 7; i++) { +// data.push({ +// parsing: true, +// label: names[i], +// data: Array.from(keys, () => Math.round(Math.random()*1.33))}) +// } async function main() { const vote_response = await fetch(document.URL+"/votes"); if (!vote_response.ok) { @@ -23,41 +32,19 @@ async function main() { return; } - - // Sort by label to maintain the same graph order, as it goes through a hash map in the backend. - datasets.sort((a, b) => a.label > b.label) - for (let i = 0; i < datasets.length; i++) { - datasets[i].backgroundColor = colors[i % colors.length] - } - const chart_canvas = document.getElementById("vote_chart") let chart - let previous_orientation function create_chart(keys, data) { let main_axis; let aspect_ratio; - let orientation - - if (window.innerWidth / window.innerHeight > limit_ratio) { - orientation = "landscape" + if (window.innerWidth > window.innerHeight) { main_axis = 'x' aspect_ratio = 2 } else { - orientation = "portrait" main_axis = 'y' aspect_ratio = 0.5 } - // Don't re-create the chart for no reason. - if (orientation === previous_orientation) { - console.log("bijour") - return; - } else { - console.log("badour") - } - - previous_orientation = orientation - if ( chart ) { chart.destroy() } @@ -79,13 +66,6 @@ async function main() { y: { stacked: true } - }, - plugins: { - title: { - display: true, - position: 'bottom', - text: 'Répartition des suppositions' - } } } }) @@ -95,7 +75,7 @@ async function main() { create_chart(keys, datasets) } - const orientation_query = matchMedia(`(aspect-ratio < ${limit_ratio})`); + const orientation_query = matchMedia("screen and (orientation:portrait)"); orientation_query.onchange = update_chart_ratio create_chart(keys, datasets) diff --git a/templates/index.html.tera b/templates/index.html.tera index c2f3afa..ae99a96 100644 --- a/templates/index.html.tera +++ b/templates/index.html.tera @@ -12,29 +12,12 @@ -{#{% import "week_change_arrows" as week_macro %}#} -{# For some reason the import does not work ? Figure it out at some point... #} -{%- macro display(display_character, to, enabled) -%} - {%- set class = "week_change" -%} - {%- if enabled == true %} - {% set target = ("href=/" ~ to) %} - {%- else -%} - {% set class = class ~ " week_change_hidden" -%} - {% set target = "" %} - {%- endif -%} - {%- if enabled == true -%}{{- display_character -}}{%- endif -%} -{%- endmacro display -%} - - -{% set back_arrow_enabled = week_data.number > 1 %} -{% set next_arrow_enabled = (week_data.is_last_week != true or user.is_admin == true) %} -{% set next_arrow_href = (week_data.number + 1) %} -{% if user.is_admin == true %} - {% set next_arrow_href = next_arrow_href ~ "/create" %} - {% set next_arrow_chara = '⥅' %} +{# Check if the user has a vote in advance, for readability #} +{% if user.logged_in == true and user.has_week_vote == true%} + {% set has_vote = true %} {% else %} - {% set next_arrow_chara = '⟹' %} -{% endif %} + {% set has_vote = false %} +{% endif -%}
@@ -42,7 +25,7 @@ {% if user.logged_in == true %}

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

{% else %} - + @@ -50,18 +33,9 @@ {% endif %}
-

{{- self::display(display_character='⟸', to=(week_data.number - 1), enabled=back_arrow_enabled) }} - Semaine {{ week_data.number }} - {{- self::display(display_character=next_arrow_chara, to=next_arrow_href, enabled=next_arrow_enabled) -}}

+

Semaine {{ week_data.number }}

- {% if user.is_admin == true and week_data.is_last_week != true %} - - - - {% endif %}
{{ week_data.rendered_text | safe }} {%- if user.is_admin == true -%} @@ -104,7 +78,7 @@ {% if user.logged_in == true and user.is_admin == false %}