Compare commits
13 commits
8547312a78
...
a0b79a17ea
Author | SHA1 | Date | |
---|---|---|---|
a0b79a17ea | |||
e08a46af3a | |||
635716c04b | |||
f41f5142c9 | |||
dfdd079ac4 | |||
207ce6c1d2 | |||
2b3dd28fed | |||
f7e1218f21 | |||
f74ed20e80 | |||
4c89a0783d | |||
d0843d600e | |||
8fd8ce8220 | |||
982a7ffd65 |
10 changed files with 254 additions and 70 deletions
|
@ -7,15 +7,17 @@ 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" !
|
A list of things that could be implemented/added to the application, some of them are needed for "feature completeness" !
|
||||||
|
|
||||||
- [ ] Being able to change from one week to the next
|
- [x] Being able to change from one week to the next
|
||||||
- [ ] Create new weeks for the admin
|
- [x] Create new weeks for the admin
|
||||||
- [ ] Proper week redirection
|
- [x] Proper week redirection
|
||||||
|
- [ ] Correctly handle non-existing week number
|
||||||
- [x] Add introduction to the weekly truths
|
- [x] Add introduction to the weekly truths
|
||||||
- [ ] Bundle static assets in the binary
|
- [ ] Bundle static assets in the binary
|
||||||
- [ ] Move the database queries to their own functions
|
- [ ] Move the database queries to their own functions
|
||||||
- [ ] Cache those results
|
- [ ] Cache those results
|
||||||
- [ ] Centralize Markdown parsing ?
|
- [ ] Centralize Markdown parsing ?
|
||||||
- [ ] Use fairings for the different elements ?
|
- [ ] Use fairings for the different elements ?
|
||||||
|
- [ ] Use guards for User calls ?
|
||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
|
|
||||||
|
|
19
src/auth.rs
19
src/auth.rs
|
@ -9,7 +9,7 @@ use argon2::{Argon2, PasswordHash, PasswordVerifier};
|
||||||
use blake2::{Blake2b512, Digest};
|
use blake2::{Blake2b512, Digest};
|
||||||
use blake2::digest::FixedOutput;
|
use blake2::digest::FixedOutput;
|
||||||
use crate::database_records::{AuthTokens, PlayerLoginInfo, Vote};
|
use crate::database_records::{AuthTokens, PlayerLoginInfo, Vote};
|
||||||
use crate::database;
|
use crate::{database, week};
|
||||||
use database::Db;
|
use database::Db;
|
||||||
|
|
||||||
// TODO: Make FromRequest guard https://api.rocket.rs/v0.5/rocket/request/trait.FromRequest and split admin
|
// TODO: Make FromRequest guard https://api.rocket.rs/v0.5/rocket/request/trait.FromRequest and split admin
|
||||||
|
@ -86,7 +86,8 @@ pub async fn get_user(week: u8, db: &mut Connection<Db>, cookies: &CookieJar<'_>
|
||||||
(String::new(), false)
|
(String::new(), false)
|
||||||
};
|
};
|
||||||
|
|
||||||
let votes: Vec<Vote> = if logged_in {
|
// TODO: Move to src/vote.rs
|
||||||
|
let votes: Vec<Vote> = if logged_in && !is_admin {
|
||||||
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;")
|
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(week)
|
||||||
.bind(&id_str)
|
.bind(&id_str)
|
||||||
|
@ -125,8 +126,8 @@ pub struct AuthForm {
|
||||||
password: String
|
password: String
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/login", data="<form>")]
|
#[post("/<week>/login", data="<form>")]
|
||||||
pub async fn login(form: Form<AuthForm>, mut db: Connection<Db>, cookies: &CookieJar<'_>) -> Redirect {
|
pub async fn login(week: u8, form: Form<AuthForm>, mut db: Connection<Db>, cookies: &CookieJar<'_>) -> Redirect {
|
||||||
let user_search: Result<PlayerLoginInfo, _> = sqlx::query_as("SELECT id, is_admin, name, pwd_hash FROM Players WHERE name == $1")
|
let user_search: Result<PlayerLoginInfo, _> = sqlx::query_as("SELECT id, is_admin, name, pwd_hash FROM Players WHERE name == $1")
|
||||||
.bind(&form.name)
|
.bind(&form.name)
|
||||||
.fetch_one(&mut **db)
|
.fetch_one(&mut **db)
|
||||||
|
@ -135,7 +136,7 @@ pub async fn login(form: Form<AuthForm>, mut db: Connection<Db>, cookies: &Cooki
|
||||||
if user_search.is_err() {
|
if user_search.is_err() {
|
||||||
error!("Login failed : invalid user {:?}, err: {:?}", form.name, user_search.err());
|
error!("Login failed : invalid user {:?}, err: {:?}", form.name, user_search.err());
|
||||||
cookies.add(("toast_error", "Impossible de se connecter !"));
|
cookies.add(("toast_error", "Impossible de se connecter !"));
|
||||||
return Redirect::to(uri!("/"));
|
return Redirect::to(uri!(week::week(week)));
|
||||||
}
|
}
|
||||||
let new_user = user_search.unwrap();
|
let new_user = user_search.unwrap();
|
||||||
|
|
||||||
|
@ -143,7 +144,7 @@ pub async fn login(form: Form<AuthForm>, mut db: Connection<Db>, cookies: &Cooki
|
||||||
if password_hash_parse.is_err() {
|
if password_hash_parse.is_err() {
|
||||||
error!("Login failed : could not parse password hash {:?}", password_hash_parse.err());
|
error!("Login failed : could not parse password hash {:?}", password_hash_parse.err());
|
||||||
cookies.add(("toast_error", "Impossible de se connecter !"));
|
cookies.add(("toast_error", "Impossible de se connecter !"));
|
||||||
return Redirect::to(uri!("/"));
|
return Redirect::to(uri!(week::week(week)));
|
||||||
}
|
}
|
||||||
let password_hash = password_hash_parse.unwrap();
|
let password_hash = password_hash_parse.unwrap();
|
||||||
|
|
||||||
|
@ -167,7 +168,7 @@ pub async fn login(form: Form<AuthForm>, mut db: Connection<Db>, cookies: &Cooki
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
error!("Login failed : coult not store auth token in database : {error}");
|
error!("Login failed : coult not store auth token in database : {error}");
|
||||||
cookies.add(("toast_error", "Impossible de se connecter !"));
|
cookies.add(("toast_error", "Impossible de se connecter !"));
|
||||||
return Redirect::to(uri!("/"));
|
return Redirect::to(uri!(week::week(week)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,11 +178,11 @@ pub async fn login(form: Form<AuthForm>, mut db: Connection<Db>, cookies: &Cooki
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("Login failed : invalid password for {:?}\nError : {err}", new_user.name);
|
error!("Login failed : invalid password for {:?}\nError : {err}", new_user.name);
|
||||||
cookies.add(("toast_error", "Impossible de se connecter !"));
|
cookies.add(("toast_error", "Impossible de se connecter !"));
|
||||||
return Redirect::to(uri!("/"));
|
return Redirect::to(uri!(week::week(week)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Redirect::to(uri!("/"))
|
Redirect::to(uri!(week::week(week)))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bypass_auth_debug(cookies: &CookieJar<'_>) {
|
pub fn bypass_auth_debug(cookies: &CookieJar<'_>) {
|
||||||
|
|
11
src/main.rs
11
src/main.rs
|
@ -12,6 +12,8 @@ use rocket_db_pools::{sqlx, sqlx::Row, Database, Connection};
|
||||||
use sqlx::Error;
|
use sqlx::Error;
|
||||||
|
|
||||||
mod auth;
|
mod auth;
|
||||||
|
use auth::User;
|
||||||
|
|
||||||
mod truth;
|
mod truth;
|
||||||
mod vote;
|
mod vote;
|
||||||
mod week;
|
mod week;
|
||||||
|
@ -19,9 +21,8 @@ mod week;
|
||||||
|
|
||||||
mod database;
|
mod database;
|
||||||
mod database_records;
|
mod database_records;
|
||||||
use database::Db;
|
|
||||||
use database_records::*;
|
use database_records::*;
|
||||||
use auth::User;
|
use database::Db;
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
async fn index(mut db: Connection<Db>) -> Redirect {
|
async fn index(mut db: Connection<Db>) -> Redirect {
|
||||||
|
@ -41,7 +42,11 @@ async fn index(mut db: Connection<Db>) -> Redirect {
|
||||||
fn rocket() -> _ {
|
fn rocket() -> _ {
|
||||||
rocket::build()
|
rocket::build()
|
||||||
.mount("/", FileServer::from(relative!("static_files")))
|
.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, auth::login])
|
.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])
|
||||||
.attach(database::stage())
|
.attach(database::stage())
|
||||||
.attach(Template::fairing())
|
.attach(Template::fairing())
|
||||||
}
|
}
|
||||||
|
|
14
src/truth.rs
14
src/truth.rs
|
@ -6,7 +6,7 @@ use rocket_db_pools::{sqlx, Connection};
|
||||||
|
|
||||||
use pulldown_cmark::{Parser, Options};
|
use pulldown_cmark::{Parser, Options};
|
||||||
use sqlx::Row;
|
use sqlx::Row;
|
||||||
use crate::{auth, database};
|
use crate::{auth, database, week};
|
||||||
|
|
||||||
#[derive(FromForm)]
|
#[derive(FromForm)]
|
||||||
pub struct TruthUpdateForm {
|
pub struct TruthUpdateForm {
|
||||||
|
@ -20,7 +20,7 @@ pub async fn edit_truth(week: u8, truth_number: u8, form: Form<TruthUpdateForm>,
|
||||||
let user = auth::get_user(week, &mut db, cookies).await;
|
let user = auth::get_user(week, &mut db, cookies).await;
|
||||||
if !user.is_admin {
|
if !user.is_admin {
|
||||||
cookies.add(("toast_error", "Vous n'avez pas la permission de changer la vérité."));
|
cookies.add(("toast_error", "Vous n'avez pas la permission de changer la vérité."));
|
||||||
return Redirect::to(uri!("/"));
|
return Redirect::to(uri!(week::week(week)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut options = Options::empty();
|
let mut options = Options::empty();
|
||||||
|
@ -50,16 +50,16 @@ pub async fn edit_truth(week: u8, truth_number: u8, form: Form<TruthUpdateForm>,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Redirect::to(uri!("/"))
|
Redirect::to(uri!(week::week(week)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/<week>/new", data="<form>")]
|
#[post("/<week>/new_truth", data="<form>")]
|
||||||
pub async fn create_truth(week: u8, form: Form<TruthUpdateForm>,
|
pub async fn create_truth(week: u8, form: Form<TruthUpdateForm>,
|
||||||
mut db: Connection<database::Db>, cookies: &CookieJar<'_>) -> Redirect {
|
mut db: Connection<database::Db>, cookies: &CookieJar<'_>) -> Redirect {
|
||||||
let user = auth::get_user(week, &mut db, cookies).await;
|
let user = auth::get_user(week, &mut db, cookies).await;
|
||||||
if !user.is_admin {
|
if !user.is_admin {
|
||||||
cookies.add(("toast_error", "Vous n'avez pas la permission d'ajouter de vérité."));
|
cookies.add(("toast_error", "Vous n'avez pas la permission d'ajouter de vérité."));
|
||||||
return Redirect::to(uri!("/"));
|
return Redirect::to(uri!(week::week(week)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let truth_number: u8 = match sqlx::query("SELECT max(number) from Truths WHERE week == $1;")
|
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<TruthUpdateForm>,
|
||||||
if truth_number == 0 {
|
if truth_number == 0 {
|
||||||
error!("Error while getting max truth number.");
|
error!("Error while getting max truth number.");
|
||||||
cookies.add(("toast_error", "Erreur lors de l'ajout de la vérité..."));
|
cookies.add(("toast_error", "Erreur lors de l'ajout de la vérité..."));
|
||||||
return Redirect::to(uri!("/"));
|
return Redirect::to(uri!(week::week(week)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut options = Options::empty();
|
let mut options = Options::empty();
|
||||||
|
@ -108,5 +108,5 @@ pub async fn create_truth(week: u8, form: Form<TruthUpdateForm>,
|
||||||
|
|
||||||
debug!("Truth was successfully added");
|
debug!("Truth was successfully added");
|
||||||
|
|
||||||
Redirect::to(uri!("/"))
|
Redirect::to(uri!(week::week(week)))
|
||||||
}
|
}
|
||||||
|
|
18
src/vote.rs
18
src/vote.rs
|
@ -2,6 +2,7 @@ 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};
|
||||||
|
@ -9,7 +10,7 @@ use rocket::serde::json::Json;
|
||||||
|
|
||||||
use rocket_db_pools::{sqlx, Connection};
|
use rocket_db_pools::{sqlx, Connection};
|
||||||
|
|
||||||
use crate::{auth, database};
|
use crate::{auth, database, week};
|
||||||
use crate::database_records::{Vote, VotingData};
|
use crate::database_records::{Vote, VotingData};
|
||||||
|
|
||||||
#[derive(FromForm)]
|
#[derive(FromForm)]
|
||||||
|
@ -24,7 +25,7 @@ pub async fn vote(week: u8, form: Form<VoteForm>,
|
||||||
|
|
||||||
if !user.logged_in {
|
if !user.logged_in {
|
||||||
cookies.add(("toast_error", "Vous n'avez pas la permission de changer de vote."));
|
cookies.add(("toast_error", "Vous n'avez pas la permission de changer de vote."));
|
||||||
return Redirect::to(uri!("/"));
|
return Redirect::to(uri!(week::week(week)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let filtered_votes = form.truth_votes.iter().filter_map(
|
let filtered_votes = form.truth_votes.iter().filter_map(
|
||||||
|
@ -61,7 +62,7 @@ pub async fn vote(week: u8, form: Form<VoteForm>,
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
debug!("Player {:?} voting {voted_id} for truth {truth_id}", user.id);
|
debug!("Player {:?} voting {voted_id} for truth {truth_id}", user.id);
|
||||||
// TODO: Find a way to use only one statement ?
|
// TODO: Find a way to use only one statement ? --> query_many
|
||||||
// Cannot all launch and await because all connect to DB
|
// 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);")
|
match sqlx::query("INSERT INTO Votes (truth_id, voter_id, voted_id) VALUES ($1, $2, $3);")
|
||||||
.bind(truth_id)
|
.bind(truth_id)
|
||||||
|
@ -84,7 +85,7 @@ pub async fn vote(week: u8, form: Form<VoteForm>,
|
||||||
debug!("Vote successful")
|
debug!("Vote successful")
|
||||||
}
|
}
|
||||||
|
|
||||||
Redirect::to(uri!("/"))
|
Redirect::to(uri!(week::week(week)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
|
@ -110,11 +111,18 @@ pub async fn fetch_vote_data(week: u8, mut db: Connection<database::Db>) -> Opti
|
||||||
});
|
});
|
||||||
|
|
||||||
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});
|
||||||
if vote_count < 17 {
|
// 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) {
|
||||||
return None;
|
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::<String, Vec<u8>>::new();
|
let mut vote_data = HashMap::<String, Vec<u8>>::new();
|
||||||
let mut next_truth_number = 1;
|
let mut next_truth_number = 1;
|
||||||
for raw_vote in raw_votes {
|
for raw_vote in raw_votes {
|
||||||
|
|
102
src/week.rs
102
src/week.rs
|
@ -6,6 +6,7 @@ use rocket::response::Redirect;
|
||||||
|
|
||||||
use rocket_db_pools::{sqlx, Connection};
|
use rocket_db_pools::{sqlx, Connection};
|
||||||
use rocket_dyn_templates::{context, Template};
|
use rocket_dyn_templates::{context, Template};
|
||||||
|
use sqlx::{Acquire, Executor};
|
||||||
use crate::auth;
|
use crate::auth;
|
||||||
use crate::auth::User;
|
use crate::auth::User;
|
||||||
use crate::database::Db;
|
use crate::database::Db;
|
||||||
|
@ -21,7 +22,7 @@ pub async fn week(week_number: u8, mut db: Connection<Db>, cookies: &CookieJar<'
|
||||||
.fetch_all(&mut **db).await {
|
.fetch_all(&mut **db).await {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(error) => {
|
Err(error) => {
|
||||||
println!("Some error while getting players : {error}");
|
error!("Some error while getting players : {error}");
|
||||||
Vec::<Player>::new()
|
Vec::<Player>::new()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -84,7 +85,7 @@ pub async fn update_week(week: u8, raw_intro: Form<String>,
|
||||||
let user = auth::get_user(week, &mut db, cookies).await;
|
let user = auth::get_user(week, &mut db, cookies).await;
|
||||||
if !user.is_admin {
|
if !user.is_admin {
|
||||||
cookies.add(("toast_error", "Vous n'avez pas la permission de changer la semaine."));
|
cookies.add(("toast_error", "Vous n'avez pas la permission de changer la semaine."));
|
||||||
return Redirect::to(uri!("/"));
|
return Redirect::to(uri!(week(week)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut options = Options::empty();
|
let mut options = Options::empty();
|
||||||
|
@ -112,5 +113,100 @@ pub async fn update_week(week: u8, raw_intro: Form<String>,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Redirect::to(uri!("/"))
|
Redirect::to(uri!(week(week)))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/<week>/set_last")]
|
||||||
|
pub async fn set_last_week(week: u8, mut db: Connection<Db>, 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("/<week>/create")]
|
||||||
|
pub async fn create_week(week: u8, mut db: Connection<Db>, 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<Option<u8>, 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)))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.editor {
|
.editor {
|
||||||
width: 100%;
|
width: calc(100% - 1.25em); /* The width is calculated *inside* the padding, so adjust for it. */
|
||||||
height: 6eM;
|
height: 6eM;
|
||||||
font-size: large;
|
font-size: large;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
|
@ -47,6 +47,34 @@
|
||||||
top: 25%;
|
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 {
|
hr {
|
||||||
border: none;
|
border: none;
|
||||||
border-top: 2px solid #9ec5fe;
|
border-top: 2px solid #9ec5fe;
|
||||||
|
@ -74,13 +102,3 @@ body {
|
||||||
body > h2 {
|
body > h2 {
|
||||||
padding-left: 0.25eM;
|
padding-left: 0.25eM;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (orientation: portrait) {
|
|
||||||
.truth_list {
|
|
||||||
width: 60vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
.graph {
|
|
||||||
width: 35vw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,14 +1,5 @@
|
||||||
|
const limit_ratio = 1.3
|
||||||
// const names = ["Bystus", "Dory", "Fen", "Lucky", "Nico", "Peran", "trot"]
|
const colors = ['#b6b8fc', '#b6f4fc', '#fcb6cc', '#e0fcb6', '#fcdcb6', '#b6fcc8', '#f0b6fc']
|
||||||
//
|
|
||||||
//
|
|
||||||
// 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() {
|
async function main() {
|
||||||
const vote_response = await fetch(document.URL+"/votes");
|
const vote_response = await fetch(document.URL+"/votes");
|
||||||
if (!vote_response.ok) {
|
if (!vote_response.ok) {
|
||||||
|
@ -32,19 +23,41 @@ async function main() {
|
||||||
return;
|
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")
|
const chart_canvas = document.getElementById("vote_chart")
|
||||||
let chart
|
let chart
|
||||||
|
let previous_orientation
|
||||||
function create_chart(keys, data) {
|
function create_chart(keys, data) {
|
||||||
let main_axis;
|
let main_axis;
|
||||||
let aspect_ratio;
|
let aspect_ratio;
|
||||||
if (window.innerWidth > window.innerHeight) {
|
let orientation
|
||||||
|
|
||||||
|
if (window.innerWidth / window.innerHeight > limit_ratio) {
|
||||||
|
orientation = "landscape"
|
||||||
main_axis = 'x'
|
main_axis = 'x'
|
||||||
aspect_ratio = 2
|
aspect_ratio = 2
|
||||||
} else {
|
} else {
|
||||||
|
orientation = "portrait"
|
||||||
main_axis = 'y'
|
main_axis = 'y'
|
||||||
aspect_ratio = 0.5
|
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 ) {
|
if ( chart ) {
|
||||||
chart.destroy()
|
chart.destroy()
|
||||||
}
|
}
|
||||||
|
@ -66,6 +79,13 @@ async function main() {
|
||||||
y: {
|
y: {
|
||||||
stacked: true
|
stacked: true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
plugins: {
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
position: 'bottom',
|
||||||
|
text: 'Répartition des suppositions'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -75,7 +95,7 @@ async function main() {
|
||||||
create_chart(keys, datasets)
|
create_chart(keys, datasets)
|
||||||
}
|
}
|
||||||
|
|
||||||
const orientation_query = matchMedia("screen and (orientation:portrait)");
|
const orientation_query = matchMedia(`(aspect-ratio < ${limit_ratio})`);
|
||||||
orientation_query.onchange = update_chart_ratio
|
orientation_query.onchange = update_chart_ratio
|
||||||
|
|
||||||
create_chart(keys, datasets)
|
create_chart(keys, datasets)
|
||||||
|
|
|
@ -12,12 +12,29 @@
|
||||||
<script defer="defer" type="text/javascript" src="/vote_chart.js"></script>
|
<script defer="defer" type="text/javascript" src="/vote_chart.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
{# Check if the user has a vote in advance, for readability #}
|
{#{% import "week_change_arrows" as week_macro %}#}
|
||||||
{% if user.logged_in == true and user.has_week_vote == true%}
|
{# For some reason the import does not work ? Figure it out at some point... #}
|
||||||
{% set has_vote = true %}
|
{%- 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 -%}
|
||||||
|
<a {{ target }} class="{{ class }}">{%- if enabled == true -%}{{- display_character -}}{%- endif -%}</a>
|
||||||
|
{%- 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 = '⥅' %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% set has_vote = false %}
|
{% set next_arrow_chara = '⟹' %}
|
||||||
{% endif -%}
|
{% endif %}
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="top_bar">
|
<div class="top_bar">
|
||||||
|
@ -25,7 +42,7 @@
|
||||||
{% if user.logged_in == true %}
|
{% if user.logged_in == true %}
|
||||||
<p>Connecté en tant que <b>{{ user.name }}</b></p>
|
<p>Connecté en tant que <b>{{ user.name }}</b></p>
|
||||||
{% else %}
|
{% else %}
|
||||||
<form class="login" id="login" action="/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>
|
||||||
<label>Mot de passe <input form="login" type="password" name="password"/></label>
|
<label>Mot de passe <input form="login" type="password" name="password"/></label>
|
||||||
<button form="login">Se connecter</button>
|
<button form="login">Se connecter</button>
|
||||||
|
@ -33,9 +50,18 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2>Semaine {{ week_data.number }}</h2>
|
<h2>{{- 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) -}}</h2>
|
||||||
<div class="page_body">
|
<div class="page_body">
|
||||||
<div class="truth_list">
|
<div class="truth_list">
|
||||||
|
{% if user.is_admin == true and week_data.is_last_week != true %}
|
||||||
|
<form action="/{{ week_data.number }}/set_last" method="post">
|
||||||
|
<button>
|
||||||
|
Définir comme dernière semaine active
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
<div class="week_intro">
|
<div class="week_intro">
|
||||||
{{ week_data.rendered_text | safe }}
|
{{ week_data.rendered_text | safe }}
|
||||||
{%- if user.is_admin == true -%}
|
{%- if user.is_admin == true -%}
|
||||||
|
@ -78,7 +104,7 @@
|
||||||
{% if user.logged_in == true and user.is_admin == false %}
|
{% if user.logged_in == true and user.is_admin == false %}
|
||||||
<br/>
|
<br/>
|
||||||
<button form="truths">
|
<button form="truths">
|
||||||
{%- if has_vote == true -%}
|
{%- if user.logged_in == true and user.has_week_vote == true -%}
|
||||||
Changer de vote
|
Changer de vote
|
||||||
{% else %}
|
{% else %}
|
||||||
À voter !
|
À voter !
|
||||||
|
@ -91,7 +117,7 @@
|
||||||
{% if user.is_admin == true %}
|
{% if user.is_admin == true %}
|
||||||
<div class="individual_truth">
|
<div class="individual_truth">
|
||||||
<h3>Nouvelle vérité</h3>
|
<h3>Nouvelle vérité</h3>
|
||||||
<form action="/{{ week_data.number }}/new" method="POST">
|
<form action="/{{ week_data.number }}/new_truth" method="POST">
|
||||||
{% include "truth_editor" %}
|
{% include "truth_editor" %}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
{%- set is_disabled = "" -%}
|
||||||
|
{# If we are not during the active week, prevent changing vote but not sending a missing one. #}
|
||||||
|
{%- if week_data.is_last_week != true and user.votes | filter(attribute="truth_id", value=truth.id) -%}
|
||||||
|
{%- set is_disabled = "disabled" -%}
|
||||||
|
{%- endif -%}
|
||||||
|
|
||||||
<div class="individual_truth">
|
<div class="individual_truth">
|
||||||
<h3>Vérité {{ truth.number }}</h3>
|
<h3>Vérité {{ truth.number }}</h3>
|
||||||
<p>{{ truth.rendered_text | safe }}</p>
|
<p>{{ truth.rendered_text | safe }}</p>
|
||||||
|
@ -8,15 +14,17 @@
|
||||||
Tu l'as fait :)
|
Tu l'as fait :)
|
||||||
{%- else -%}
|
{%- else -%}
|
||||||
Qui l'a fait ?
|
Qui l'a fait ?
|
||||||
<select form="truths" name="truth_votes[{{ truth.id }}]">
|
<select form="truths" name="truth_votes[{{ truth.id }}]" {{ is_disabled }}>
|
||||||
<option value="0">---</option>
|
<option value="0">---</option>
|
||||||
{% for player in other_players %}
|
{% for player in other_players %}
|
||||||
{# Check if we should pre-select an existing vote #}
|
{# Check if we should pre-select an existing vote. #}
|
||||||
{% if has_vote == true and player.id == user.votes[truth_index].voted_id %}
|
{% set_global is_selected = "" %}
|
||||||
{% set is_selected = "selected" %}
|
{% for vote in user.votes %}
|
||||||
{% else %}
|
{% if truth.id == vote.truth_id and player.id == vote.voted_id %}
|
||||||
{% set is_selected = "" %}
|
{% set_global is_selected = "selected" %}
|
||||||
{% endif %}
|
{% break %}
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
<option value="{{ player.id }}" {{- is_selected -}}>{{ player.name }}</option>
|
<option value="{{ player.id }}" {{- is_selected -}}>{{ player.name }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue