diff --git a/Cargo.lock b/Cargo.lock index b3eece5..ae3db85 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -578,7 +578,7 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "fabula_votes_server" -version = "1.4.1" +version = "1.0.0" dependencies = [ "argon2", "blake2", diff --git a/Cargo.toml b/Cargo.toml index 298d596..27aca85 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.4.1" +version = "1.0.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/README.md b/README.md index c2e95a2..dc5d83a 100644 --- a/README.md +++ b/README.md @@ -7,32 +7,25 @@ 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 - - [x] Add introduction to the weekly truths + - [ ] Being able to change from one week to the next + - [ ] Create new weeks for the admin + - [ ] Proper week redirection + - [ ] Add introduction to the weekly truths - [ ] Bundle static assets in the binary - - [ ] Move the database queries to their own functions + - [ ] Move the databse queries to their own functions - [ ] Cache those results - [ ] Centralize Markdown parsing ? - - [x] Use fairings for the different elements - - [ ] Use guards for User calls ? - - [ ] Use SQLite Row ID for User IDs rather than regular IDs, for randomness ? - - [x] Split user from vote data + - [ ] Use fairings for the different elements ? # Dependencies -This project currently uses, for the backend : +This project currently uses : - [Rocket](https://docs.rs/rocket/0.5.1/rocket/), for the web application backend - [SQLX](https://docs.rs/sqlx/0.7.4/sqlx/), for database access (this is only expeceted to be used with SQLite) - [Tera](https://docs.rs/tera/latest/tera/), for templating - [Argon2](https://docs.rs/argon2/latest/argon2/), for password hashing - [Pull_down CMark](https://docs.rs/pulldown-cmark/0.11.0/pulldown_cmark/), for markdown rendering -For the frontend : - - [Chart.js](https://www.chartjs.org/), for rendering the vote graph. - # License The code present in this repository is licensed under the Mozilla Public License 2.0. diff --git a/db/03_create-week-table.sql b/db/03_create-week-table.sql deleted file mode 100644 index 1b24dd7..0000000 --- a/db/03_create-week-table.sql +++ /dev/null @@ -1,10 +0,0 @@ -CREATE TABLE IF NOT EXISTS Weeks ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - number INTEGER NOT NULL CHECK (number > 0), - is_last_week INTEGER NOT NULL, - rendered_text VARCHAR NOT NULL, - raw_text VARCHAR NOT NULL -); - --- This is to upgrade from version 1.0 to 1.1 with an existing database -INSERT INTO Weeks (number, is_last_week, rendered_text, raw_text) VALUES (1, 1, "", ""); diff --git a/db/04_create-tags-tables.sql b/db/04_create-tags-tables.sql deleted file mode 100644 index e7bb714..0000000 --- a/db/04_create-tags-tables.sql +++ /dev/null @@ -1,13 +0,0 @@ -CREATE TABLE IF NOT EXISTS Tags ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - name VARCHAR NOT NULL UNIQUE, - color VARCHAR NOT NULL -); - -CREATE TABLE IF NOT EXISTS TaggedTruths ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - truth_id INTEGER NOT NULL, - tag_id INTEGER NOT NULL, - FOREIGN KEY (truth_id) REFERENCES Truths(id) ON DELETE CASCADE, - FOREIGN KEY (tag_id) REFERENCES Tags(id) ON DELETE CASCADE - ); diff --git a/src/auth.rs b/src/auth.rs index 672b79d..4b9df6c 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -8,9 +8,9 @@ use std::time::{SystemTime, UNIX_EPOCH}; use argon2::{Argon2, PasswordHash, PasswordVerifier}; use blake2::{Blake2b512, Digest}; use blake2::digest::FixedOutput; -use rocket::fairing::AdHoc; -use crate::database_records::{AuthTokens, PlayerLoginInfo}; -use crate::{database, week}; +use sqlx::Error; +use crate::database_records::{AuthTokens, PlayerLoginInfo, Vote}; +use crate::database; use database::Db; // TODO: Make FromRequest guard https://api.rocket.rs/v0.5/rocket/request/trait.FromRequest and split admin @@ -21,9 +21,11 @@ pub struct User { pub is_admin: bool, pub id: u16, pub name: String, + pub has_week_vote: bool, + pub votes: Vec } -pub async fn get_user(db: &mut Connection, cookies: &CookieJar<'_>) -> User { +pub async fn get_user(week: u8, db: &mut Connection, cookies: &CookieJar<'_>) -> User { let auth_token: Option = match cookies.get_private("auth_token") { Some(cookie) => Some(cookie.value().to_string()), None => None @@ -55,7 +57,7 @@ pub async fn get_user(db: &mut Connection, cookies: &CookieJar<'_>) -> User Vec::::new() } }; - tokens.iter().find(|auth_token| {auth_token.token.eq(&token_str)}).is_some() + tokens.iter().find(|auth_token| {println!("Token : {:?}\nCookie : {:?}", auth_token.token, token_str); auth_token.token.eq(&token_str)}).is_some() } else { id_str = String::new(); false @@ -85,12 +87,26 @@ pub async fn get_user(db: &mut Connection, cookies: &CookieJar<'_>) -> User (String::new(), false) }; + 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) + .fetch_all(&mut ***db).await.unwrap_or_else(|error| { + error!("Error while getting votes : {error}"); + Vec::::new() + }) + } else { + Vec::::new() + }; + if logged_in { User { logged_in, is_admin, id: id_str.parse::().unwrap(), name, + has_week_vote: if votes.is_empty() { false } else { true }, + votes } } else { User { @@ -98,6 +114,8 @@ pub async fn get_user(db: &mut Connection, cookies: &CookieJar<'_>) -> User is_admin: false, id: 0, name, + has_week_vote: false, + votes } } } @@ -108,8 +126,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) @@ -118,7 +136,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(); @@ -126,7 +144,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(); @@ -150,7 +168,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!("/")); } } @@ -160,41 +178,24 @@ 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!("/")) } -#[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))) +pub fn bypass_auth_debug(cookies: &CookieJar<'_>) { + if cookies.get_private("auth_token").is_some() { + return } + let mut hasher = Blake2b512::new(); + hasher.update(b"8"); + hasher.update(SystemTime::now().duration_since(UNIX_EPOCH).expect("Non monotonous time event").as_secs().to_le_bytes()); + let hash = hasher.finalize_fixed().to_ascii_lowercase(); + let hash_str = String::from_utf8_lossy(hash.as_slice()).to_ascii_lowercase(); + cookies.add_private(("auth_token", hash_str.clone())); + cookies.add_private(("auth_id", 8.to_string())); - 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 stage() -> AdHoc { - AdHoc::on_ignite("Auth stage", |rocket| async { - rocket.mount("/", routes![login, logout]) - }) -} + println!("Generated hash string : {hash_str}"); +} \ No newline at end of file diff --git a/src/database_records.rs b/src/database_records.rs index 2f49061..89c116a 100644 --- a/src/database_records.rs +++ b/src/database_records.rs @@ -19,26 +19,21 @@ pub struct PlayerLoginInfo { #[derive(sqlx::FromRow, Deserialize, Serialize)] #[serde(crate = "rocket::serde")] pub struct Truth { - pub id: u32, + id: u32, week: u8, number: u8, author_id: u16, rendered_text: String, raw_text: String, - #[sqlx(skip)] - pub tags: Vec, } #[derive(sqlx::FromRow, Deserialize, Serialize)] #[serde(crate = "rocket::serde")] pub struct DisplayTruth { - pub id: u32, - week: u8, + id: u32, number: u8, author_id: u16, rendered_text: String, - #[sqlx(skip)] - pub tags: Vec, } #[derive(sqlx::FromRow, Deserialize, Serialize)] @@ -63,20 +58,3 @@ pub struct VotingData { pub struct AuthTokens { pub token: String, } - -#[derive(sqlx::FromRow, Deserialize, Serialize)] -#[serde(crate = "rocket::serde")] -pub struct Week { - pub number: u8, - pub is_last_week: bool, - pub rendered_text: String, - pub raw_text: String, -} - -#[derive(FromForm)] -#[derive(sqlx::FromRow, Deserialize, Serialize)] -#[serde(crate = "rocket::serde")] -pub struct Tag { - pub name: String, - pub color: String -} diff --git a/src/main.rs b/src/main.rs index bf2299c..60bfd1f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,40 +1,102 @@ #[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; +use rocket_dyn_templates::{Template, context}; -use rocket_db_pools::Connection; +use rocket_db_pools::{sqlx, sqlx::Row, Database, Connection}; +mod database_records; mod auth; -mod tag; -mod truth; mod vote; -mod week; - +mod truth; mod database; -mod database_records; use database::Db; +use database_records::*; +use auth::User; + +#[get("/")] +async fn week(week_number: u8, mut db: Connection, cookies: &CookieJar<'_>) -> Template { + let user: User = auth::get_user(week_number, &mut db, cookies).await; + + let other_players = if user.logged_in { + match sqlx::query_as("SELECT id, name FROM Players WHERE id <> $1 AND is_admin == 0 ORDER BY name") + .bind(user.id) + .fetch_all(&mut **db).await { + Ok(v) => v, + Err(error) => { + println!("Some error while getting players : {error}"); + Vec::::new() + } + } + } else { + Vec::::new() + }; + + // FIXME : This is fucking *trash* but fucking hell mate + if user.is_admin { + let truths: Vec = match sqlx::query_as("SELECT * FROM Truths WHERE week == $1 ORDER BY number") + .bind(week_number) + .fetch_all(&mut **db).await { + Ok(v) => v, + Err(error) => { + error!("Error while getting truths : {error}"); + Vec::::new() + } + }; + + Template::render("index", context! { + week_number: week_number, + truths: truths, + user: user, + other_players: other_players, + }) + } else { + let truths: Vec = match sqlx::query_as("SELECT id, number, author_id, rendered_text FROM Truths WHERE week == $1 ORDER BY number") + .bind(week_number) + .fetch_all(&mut **db).await { + Ok(v) => v, + Err(error) => { + error!("Error while getting truths : {error}"); + Vec::::new() + } + }; + + Template::render("index", context! { + week_number: week_number, + truths: truths, + user: user, + other_players: other_players, + }) + } +} #[get("/")] async fn index(mut db: Connection) -> Redirect { - let current_week: u8 = week::get_last_week(&mut db).await; + let current_week: u8 = match sqlx::query("SELECT max(week) AS last_week FROM Truths;") + .fetch_one(&mut **db).await { + Ok(v) => v.try_get(0).ok().unwrap_or_else(|| 1), // If error, go back to 1 + Err(error) => { + error!("Error while getting current week : {error:?}"); + 1 + } + }; - Redirect::to(uri!(week::week(week_number = if current_week == 0 {1} else {current_week}))) + Redirect::to(uri!("/", week(week_number = if current_week == 0 {1} else {current_week}))) } #[launch] fn rocket() -> _ { rocket::build() - .mount("/static/", FileServer::from(relative!("static_files"))) - .attach(auth::stage()) - .attach(week::stage()) - .attach(truth::stage()) - .attach(vote::stage()) - .attach(tag::stage()) - .mount("/", routes![index]) + .mount("/", FileServer::from(relative!("static_files"))) + .mount("/", routes![index, vote::fetch_vote_data, vote::vote, truth::create_truth, truth::edit_truth, week, auth::login]) .attach(database::stage()) .attach(Template::fairing()) } + +// TODO: Random Row ID \ No newline at end of file diff --git a/src/tag.rs b/src/tag.rs deleted file mode 100644 index 1069e36..0000000 --- a/src/tag.rs +++ /dev/null @@ -1,295 +0,0 @@ -use rocket::fairing::AdHoc; -use rocket::form::Form; -use rocket::http::CookieJar; -use rocket::response::Redirect; -use rocket_db_pools::Connection; -use rocket_dyn_templates::{context, Template}; -use sqlx::{Sqlite, QueryBuilder}; -use crate::database::Db; -use crate::database_records::{DisplayTruth, Tag}; - -use crate::auth; -use crate::week; - -pub async fn get_all_tags(db: &mut Connection, cookies: &CookieJar<'_>) -> Vec { - match sqlx::query_as("SELECT name, color FROM Tags") - .fetch_all(&mut ***db) - .await { - Ok(tags) => tags, - Err(error) => { - error!("Database error while fetching tags : {error}"); - cookies.add(("toast_error", "Erreur lors du chargement des tags.")); - Vec::::new() - } - } -} - -pub async fn get_truth_tags(truth_id: u32, db: &mut Connection) -> Vec { - match sqlx::query_as(" - SELECT Tags.name, Tags.color FROM Tags - JOIN TaggedTruths ON TaggedTruths.tag_id == Tags.id AND TaggedTruths.truth_id == $1 - ORDER BY Tags.id;") - .bind(truth_id) - .fetch_all(&mut ***db) - .await { - Ok(tags) => tags, - Err(error) => { - error!("Could not fetch tags for truth {:?} : {error}", truth_id); - Vec::::new() - } - } -} - -#[get("/")] -pub async fn tag_index(mut db: Connection, cookies: &CookieJar<'_>) -> Template { - let user = auth::get_user(&mut db, cookies).await; - - let tags: Vec = get_all_tags(&mut db, cookies).await; - - Template::render("tags/index", context!{ - user: user, - tags: tags - }) -} - -#[get("/filter?")] -pub async fn filtered_by_tags(tags: Vec, mut db: Connection) -> Template { - let last_week = week::get_last_week(&mut db).await; - let filtered_truths: Vec = if tags.len() == 0 { - match sqlx::query_as("SELECT id, week, number, author_id, rendered_text FROM Truths WHERE week <= $1 ORDER BY week, number;") - .bind(last_week) - .fetch_all(&mut **db) - .await { - Ok(all_truths) => all_truths, - Err(error) => { - error!("Error while fetching all truths for the filter : {error}"); - Vec::::new() - } - } - } else { - let mut query_builder: QueryBuilder = QueryBuilder::new(" - SELECT Truths.id, Truths.week, Truths.number, Truths.author_id, Truths.rendered_text FROM Truths - JOIN TaggedTruths ON Truths.id == TaggedTruths.truth_id - JOIN Tags ON Tags.id == TaggedTruths.tag_id AND Tags.name IN ( - "); - let mut separated_args = query_builder.separated(", "); - for tag in &tags { - separated_args.push_bind(tag); - } - - // Now that the comma separated list of strings is built, finish the query normally. - query_builder.push(") WHERE Truths.week <= "); - query_builder.push_bind(last_week); - query_builder.push("GROUP BY Truths.id ORDER BY Truths.week, Truths.number;"); - match query_builder.build_query_as::().fetch_all(&mut **db).await { - Ok(truths) => { - debug!("Got filtered truths by tags"); - truths - }, - Err(error) => { - error!("Error while fetching filtered truths : {error}"); - Vec::::new() - } - } - }; - - let filter_tags: Vec:: = match sqlx::query_as("SELECT name, color FROM Tags;") - .fetch_all(&mut **db) - .await { - Ok(all_tags) => { - all_tags.into_iter().filter(|tag: &Tag| tags.contains(&tag.name)).collect() - }, - Err(error) => { - error!("Error getting tags to show on filter list : {error}"); - Vec::::new() - } - }; - - Template::render("tags/filtered_truths", context!{ - tags: filter_tags, - truths: filtered_truths - }) -} - - -#[post("/create", data="")] -pub async fn create_tag(new_tag: Form, mut db: Connection, cookies: &CookieJar<'_>) -> Redirect { - let user = auth::get_user(&mut db, cookies).await; - if !user.is_admin { - cookies.add(("toast_error", "Vous n'avez pas la permission de créer une étiquette")); - return Redirect::to(uri!("/tags", tag_index)); - } - - match sqlx::query("INSERT INTO Tags (name, color) VALUES ($1, $2);") - .bind(&new_tag.name) - .bind(&new_tag.color) - .execute(&mut **db) - .await { - Ok(affected_lines) => if affected_lines.rows_affected() != 1 { - error!("Did not create tag : {:?}", &new_tag.name); - cookies.add(("toast_error", "L'étiquette n'a pas pû être créer")); - } else { - debug!("Successfully created new tag {:?}", &new_tag.name); - }, - Err(error) => { - error!("Error whilea adding tag {:?} : {error}", &new_tag.name); - cookies.add(("toast_error", "Erreur à la création de l'étiquette")); - } - }; - - Redirect::to(uri!("/tags", tag_index)) -} - -#[post("/delete/")] -pub async fn delete_tag(tag_name: &str, mut db: Connection, cookies: &CookieJar<'_>) -> Redirect { - let user = auth::get_user(&mut db, cookies).await; - if !user.is_admin { - cookies.add(("toast_error", "Vous n'avez pas la permission de supprimer une étiquette")); - return Redirect::to(uri!("/tags", tag_index)); - } - - match sqlx::query("DELETE FROM Tags WHERE name == $1;") - .bind(&tag_name) - .execute(&mut **db) - .await { - Ok(affected_lines) => if affected_lines.rows_affected() != 1 { - error!("Did not remove {tag_name}"); - cookies.add(("toast_error", "L'étiquette n'a pas pû être supprimée.")); - } - else { - debug!("Tag {tag_name} successfully removed.") - }, - Err(error) => { - error!("Error trying to remove {tag_name} : {error}"); - cookies.add(("toast_error", "Erreur lors de la suppression de l'étiquette")); - } - }; - - Redirect::to(uri!("/tags", tag_index)) -} - -#[post("/update/", data="")] -pub async fn update_tag(tag_name: &str, updated_tag: Form, mut db: Connection, cookies: &CookieJar<'_>) -> Redirect { - let user = auth::get_user(&mut db, cookies).await; - if !user.is_admin { - cookies.add(("toast_error", "Vous n'avez pas la permission de modifier une étiquette")); - return Redirect::to(uri!("/tags", tag_index)); - } - - match sqlx::query("UPDATE Tags SET name = $1, color = $2 WHERE name == $3;") - .bind(&updated_tag.name) - .bind(&updated_tag.color) - .bind(&tag_name) - .execute(&mut **db) - .await { - Ok(affected_lines) => if affected_lines.rows_affected() != 1 { - error!("Did not update tag {tag_name}"); - cookies.add(("toast_error", "L'étiquette n'a pas pû être modifiée.")); - } else { - debug!("Updated {tag_name} successfully."); - } - Err(error) => { - error!("Error while updating {tag_name} : {error}"); - cookies.add(("toast_error", "Erreur lors de la modification de l'étiquette.")); - } - }; - - Redirect::to(uri!("/tags", tag_index)) -} - -#[derive(FromForm)] -pub struct TagForm { - name: String, -} - -#[post("//tag/", data="")] -pub async fn tag_truth(week: u8, truth_id: u32, tag_form: Form, mut db: Connection, cookies: &CookieJar<'_>) -> Redirect { - let user = auth::get_user(&mut db, cookies).await; - if !user.is_admin { - cookies.add(("toast_error", "Vous n'avez pas la permission d'ajouter une étiquette")); - return Redirect::to(uri!("/tags", tag_index)); - } - - let error_cookie = || - cookies.add(("toast_error", "Erreur lors de l'ajout de l'étiquette.")); - - let tag_id: u32 = sqlx::query_scalar("SELECT id FROM Tags WHERE name == $1;") - .bind(&tag_form.name) - .fetch_one(&mut **db) - .await.unwrap_or(0); - - if tag_id == 0 { - error_cookie(); - error!("Error while trying to figure tag ID of {:?} to create the relation with {:?}.", &tag_form.name, &truth_id); - return Redirect::to(uri!(week::week(week))); - } - - match sqlx::query("INSERT INTO TaggedTruths (truth_id, tag_id) VALUES ($1, $2);") - .bind(&truth_id) - .bind(&tag_id) - .execute(&mut **db) - .await { - Ok(affected_lines) => if affected_lines.rows_affected() != 1 { - error_cookie(); - error!("Tag relation not added."); - } else { - debug!("Created tag relation with truth {truth_id} and tag {tag_id}"); - } - Err(error) => { - error_cookie(); - error!("Error while trying to add tag relation : {error}"); - } - } - - Redirect::to(uri!(week::week(week))) -} - -#[post("//untag/", data="")] -pub async fn untag_truth(week: u8, truth_id: u32, tag_form: Form, mut db: Connection, cookies: &CookieJar<'_>) -> Redirect { - let user = auth::get_user(&mut db, cookies).await; - if !user.is_admin { - cookies.add(("toast_error", "Vous n'avez pas la permission de retirer une étiquette")); - return Redirect::to(uri!("/tags", tag_index)); - } - - let error_cookie = || - cookies.add(("toast_error", "Erreur lors de la suppression de l'étiquette.")); - - let tag_id: u32 = sqlx::query_scalar("SELECT id FROM Tags WHERE name == $1;") - .bind(&tag_form.name) - .fetch_one(&mut **db) - .await.unwrap_or(0); - - if tag_id == 0 { - error_cookie(); - error!("Error while trying to figure tag ({:?}) ID to remove the relation with {:?}.", &tag_id, &truth_id); - return Redirect::to(uri!(week::week(week))); - } - - match sqlx::query("DELETE FROM TaggedTruths WHERE truth_id == $1 AND tag_id == $2;") - .bind(&truth_id) - .bind(&tag_id) - .execute(&mut **db) - .await { - Ok(affected_lines) => if affected_lines.rows_affected() != 1 { - error_cookie(); - error!("Tag relation not deleted."); - } else { - debug!("Removed tag relation with truth {truth_id} and tag {tag_id}"); - } - Err(error) => { - error_cookie(); - error!("Error while trying to remove tag relation : {error}"); - } - } - - Redirect::to(uri!(week::week(week))) -} - -pub fn stage() -> AdHoc { - AdHoc::on_ignite("Tag stage", |rocket| async { - rocket - .mount("/tags", routes![tag_index, filtered_by_tags, create_tag, update_tag, delete_tag]) - .mount("/", routes![tag_truth, untag_truth]) - }) -} diff --git a/src/truth.rs b/src/truth.rs index f3b6add..dd4c0ea 100644 --- a/src/truth.rs +++ b/src/truth.rs @@ -5,9 +5,8 @@ use rocket::response::Redirect; use rocket_db_pools::{sqlx, Connection}; use pulldown_cmark::{Parser, Options}; -use rocket::fairing::AdHoc; use sqlx::Row; -use crate::{auth, database, week}; +use crate::{auth, database}; #[derive(FromForm)] pub struct TruthUpdateForm { @@ -18,10 +17,10 @@ pub struct TruthUpdateForm { #[post("//edit/", data="")] pub async fn edit_truth(week: u8, truth_number: u8, form: Form, mut db: Connection, cookies: &CookieJar<'_>) -> Redirect { - let user = auth::get_user(&mut db, cookies).await; + 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(); @@ -51,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(&mut db, cookies).await; + 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;") @@ -77,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(); @@ -109,11 +108,5 @@ pub async fn create_truth(week: u8, form: Form, debug!("Truth was successfully added"); - Redirect::to(uri!(week::week(week))) -} - -pub fn stage() -> AdHoc { - AdHoc::on_ignite("Truth stage", |rocket| async { - rocket.mount("/", routes![create_truth, edit_truth]) - }) + Redirect::to(uri!("/")) } diff --git a/src/vote.rs b/src/vote.rs index 550707c..e5426df 100644 --- a/src/vote.rs +++ b/src/vote.rs @@ -9,32 +9,10 @@ use rocket::serde::json::Json; use rocket_db_pools::{sqlx, Connection}; -use crate::{auth, database, week}; +use crate::{auth, database}; +use crate::database::Db; use crate::database_records::{Vote, VotingData}; -#[derive(Serialize, Deserialize)] -#[serde(crate = "rocket::serde")] -pub struct WeeklyUserVotes { - pub has_week_vote: bool, - pub votes: Vec -} - -pub async fn get_weekly_user_votes(week: u8, user: &auth::User, db: &mut Connection) -> WeeklyUserVotes { - let votes: Vec = if user.logged_in && !user.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;") - .bind(week) - .bind(user.id) - .fetch_all(&mut ***db).await.unwrap_or_else(|error| { - error!("Error while getting votes : {error}"); - Vec::::new() - }) - } else { - Vec::::new() - }; - - WeeklyUserVotes {has_week_vote: if votes.is_empty() { false } else { true }, votes} -} - #[derive(FromForm)] pub struct VoteForm { truth_votes: HashMap @@ -43,11 +21,11 @@ pub struct VoteForm { #[post("//vote", data="")] pub async fn vote(week: u8, form: Form, mut db: Connection, cookies: &CookieJar<'_>) -> Redirect { - let user = auth::get_user(&mut db, cookies).await; + 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.")); - return Redirect::to(uri!(week::week(week))); + return Redirect::to(uri!("/")); } let filtered_votes = form.truth_votes.iter().filter_map( @@ -60,11 +38,9 @@ pub async fn vote(week: u8, form: Form, } ); - let existing_votes = get_weekly_user_votes(week, &user, &mut db).await; - let mut had_error = false; for (truth_id, voted_id) in filtered_votes { - match existing_votes.votes.iter().find(|vote: &&Vote| {vote.truth_id == *truth_id}) { + match user.votes.iter().find(|vote: &&Vote| {vote.truth_id == *truth_id}) { Some(vote) => { if *voted_id == vote.voted_id { continue; @@ -86,7 +62,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) @@ -109,20 +85,18 @@ pub async fn vote(week: u8, form: Form, debug!("Vote successful") } - Redirect::to(uri!(week::week(week))) + Redirect::to(uri!("/")) } #[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, cookies: &CookieJar<'_>) -> Option> { - let user = auth::get_user(&mut db, cookies).await; +pub async fn fetch_vote_data(week: u8, mut db: Connection) -> Option> { 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 @@ -136,31 +110,12 @@ pub async fn fetch_vote_data(week: u8, mut db: Connection, cookies 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. - 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)) { + 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 { @@ -172,9 +127,7 @@ pub async fn fetch_vote_data(week: u8, mut db: Connection, cookies let votes_for_player: &mut Vec = match vote_data.entry(raw_vote.votes_for) { Entry::Occupied(existing) => {existing.into_mut()} - Entry::Vacant(vacant) => { - next_truth_number = 1; // We changed user, reset to 1. - vacant.insert(Vec::::new())} + Entry::Vacant(vacant) => {vacant.insert(Vec::::new())} }; // Fill up missing spaces if we are missing any. @@ -186,11 +139,12 @@ pub async fn fetch_vote_data(week: u8, mut db: Connection, cookies next_truth_number = raw_vote.truth_number + 1; } - Some(Json(VoteData{truth_count: truth_count, votes: vote_data})) + Some(Json(VoteData{votes: vote_data})) } +// FIXME: pub fn stage() -> AdHoc { - AdHoc::on_ignite("Vote stage", |rocket| async { + AdHoc::on_ignite("SQLx Stage", |rocket| async { rocket.mount("/", routes![vote, fetch_vote_data]) }) } diff --git a/src/week.rs b/src/week.rs deleted file mode 100644 index 79608b7..0000000 --- a/src/week.rs +++ /dev/null @@ -1,246 +0,0 @@ -use pulldown_cmark::{Options, Parser}; -use rocket::fairing::AdHoc; -use rocket::form::Form; -use rocket::http::CookieJar; -use rocket::response::Redirect; - -use rocket_db_pools::{sqlx, sqlx::Row, Connection}; -use rocket_dyn_templates::{context, Template}; -use sqlx::{Acquire, Executor}; -use crate::{auth, vote}; -use crate::auth::User; -use crate::database::Db; -use crate::database_records::{DisplayTruth, Player, Tag, Truth, Week}; -use crate::tag; -use crate::vote::WeeklyUserVotes; - -pub async fn get_last_week(db: &mut Connection) -> u8 { - match sqlx::query("SELECT number FROM Weeks WHERE is_last_week == 1;") - .fetch_one(&mut ***db).await { - Ok(row) => row.try_get(0).ok().unwrap_or_else(|| 1), // If error, go back to 1 - Err(error) => { - error!("Error while getting current week : {error:?}"); - 1 - } - } -} - -#[get("/")] -pub async fn week(week_number: u8, mut db: Connection, cookies: &CookieJar<'_>) -> Template { - let user: User = auth::get_user(&mut db, cookies).await; - - let other_players = if user.logged_in { - match sqlx::query_as("SELECT id, name FROM Players WHERE id <> $1 AND is_admin == 0 ORDER BY name") - .bind(user.id) - .fetch_all(&mut **db).await { - Ok(v) => v, - Err(error) => { - error!("Some error while getting players : {error}"); - Vec::::new() - } - } - } else { - Vec::::new() - }; - - let week_data: Week = match sqlx::query_as("SELECT number, is_last_week, rendered_text, raw_text FROM Weeks WHERE number == $1") - .bind(week_number) - .fetch_one(&mut **db) - .await { - Ok(week) => week, - Err(error) => { - error!("Error while retrieving week data : {error}"); - Week {number: 0, is_last_week: true, rendered_text: "".to_string(), raw_text: "".to_string() } - } - }; - - let vote_data: WeeklyUserVotes = vote::get_weekly_user_votes(week_number,&user, &mut db).await; - - // FIXME : This is fucking *trash* but fucking hell mate - if user.is_admin { - let mut truths: Vec = match sqlx::query_as("SELECT * FROM Truths WHERE week == $1 ORDER BY number") - .bind(week_number) - .fetch_all(&mut **db).await { - Ok(v) => v, - Err(error) => { - error!("Error while getting truths : {error}"); - Vec::::new() - } - }; - - for truth in &mut truths { - truth.tags = tag::get_truth_tags(truth.id, &mut db).await; - } - - let tags: Vec = tag::get_all_tags(&mut db, cookies).await; - - Template::render("weeks/index", context! { - week_data: week_data, - truths: truths, - user: user, - other_players: other_players, - vote_data: vote_data, - tags: tags - }) - } else { - let mut truths: Vec = match sqlx::query_as("SELECT id, week, number, author_id, rendered_text FROM Truths WHERE week == $1 ORDER BY number") - .bind(week_number) - .fetch_all(&mut **db).await { - Ok(v) => v, - Err(error) => { - error!("Error while getting truths : {error}"); - Vec::::new() - } - }; - - for truth in &mut truths { - truth.tags = tag::get_truth_tags(truth.id, &mut db).await; - } - - Template::render("weeks/index", context! { - week_data: week_data, - truths: truths, - user: user, - other_players: other_players, - vote_data: vote_data - }) - } -} - -#[post("//edit", data="")] -pub async fn update_week(week: u8, raw_intro: Form, - mut db: Connection, cookies: &CookieJar<'_>) -> Redirect { - let user = auth::get_user(&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 mut options = Options::empty(); - options.insert(Options::ENABLE_STRIKETHROUGH); - options.insert(Options::ENABLE_FOOTNOTES); - options.insert(Options::ENABLE_MATH); - options.insert(Options::ENABLE_TABLES); - let markdown_parser = Parser::new_ext(raw_intro.as_str(), options); - - let mut rendered_markdown = String::new(); - pulldown_cmark::html::push_html(&mut rendered_markdown, markdown_parser); - - match sqlx::query("UPDATE Weeks SET raw_text = $1, rendered_text = $2 WHERE number == $3;") - .bind(&raw_intro.as_str()) - .bind(rendered_markdown) - .bind(week) - .fetch_optional(&mut **db) - .await { - Ok(_) => { - debug!("Week successfully updated") - } - Err(error) => { - error!("Error while updating week {week} data : {error}"); - cookies.add(("toast_error", "Il y a eu un problème lors du changement de la semaine")); - } - }; - - 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(&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(&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))) - } - } -} - -pub fn stage() -> AdHoc { - AdHoc::on_ignite("Week stage", |rocket| async { - rocket.mount("/", routes![week, create_week, update_week, set_last_week]) - }) -} diff --git a/static_files/style.css b/static_files/style.css index 0721675..7482b04 100644 --- a/static_files/style.css +++ b/static_files/style.css @@ -2,22 +2,10 @@ display: flex; flex-direction: row; justify-content: space-between; - align-items: center; margin-right: 2em; } -.top_bar_side { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: baseline; -} - -.top_bar_side > * { - padding: 0 0.5em; -} - -.truth_page_body { +.page_body { display: flex; flex-direction: row; justify-content: space-between; @@ -35,28 +23,14 @@ padding-bottom: 2em; } -.individual_truth h3 { - text-align: center; - padding-bottom: 4px; - border-bottom: slategray solid 0.15em; -} - -.individual_truth p { - padding: 0 0.5em; -} - -.editor { - width: calc(100% - 1.25em); /* The width is calculated *inside* the padding, so adjust for it. */ +.truth_editor { + width: 100%; height: 6eM; font-size: large; padding: 0.5em; margin-bottom: 1em; } -.truth_edit_form { - margin-bottom: 1em; -} - .graph { display: flex; flex-direction: column; @@ -73,131 +47,10 @@ top: 25%; } -.week_change:link, .week_change:visited, -.individual_truth > a, .individual_truth > a:link, .individual_truth > a:visited, -nav > a, nav > a:link, nav > a:visited, -h1 > a, h1 > a:link, h1 > a:visited { - color: black; - text-decoration: none; - transition: all .2s ease-in-out; -} - -.week_change:hover, .week_change:focus, -.individual_truth > a:hover, .individual_truth > a:focus, -nav > a:hover, nav > a:focus, -h1 > a:hover, h1 > a: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; - } -} - -.tag_list { - display: flex; - flex-flow: column wrap; - align-content: flex-start; - max-height: 40vh; - margin-bottom: 1em; -} - -@media (orientation: portrait) { - .tag_list { - max-height: none; - } -} - -.new_tag { - display: flex; - flex-direction: column; - border: black dashed 1px; - padding: 0.5em; - margin-top: 0.5em; - width: fit-content; -} - -.new_tag > button { - margin-top: 1em; -} - -.tag { - display: flex; - justify-content: center; - margin: 0.25em; - width: fit-content; - text-decoration: none; - text-shadow: - -1px 0 3px black, - 1px 0 3px black, - 0 -1px 3px black, - 0 1px 3px black; - color: white; - padding: 0.25em 0.75em 0.25em 0.25em; - border-bottom-left-radius: 0.4em; - border-top-left-radius: 0.4em; - border-bottom-right-radius: 1.5em 0.8em; - border-top-right-radius: 1.5em 0.8em; - - transition: all .2s ease-in-out; -} - -.tag:hover, .tag:focus { - color: mediumpurple; -} - -.truth_tags { - display: flex; - flex-flow: wrap; -} - -.truth_tags > .tag { /* We want smaller tags, the shadows must follow as well */ - font-size: smaller; - text-shadow: - -1px 0 2px black, - 1px 0 2px black, - 0 -1px 2px black, - 0 1px 2px black; -} - -.truth_tags > form { - display: contents; -} - -.login { - display: flex; - flex-direction: column; -} - -.login > * { - padding-bottom: 0.1em; -} - -.login > label { - align-self: flex-end; -} - -.login > b { - align-self: center; -} - -/* Global styling */ - hr { border: none; - border-top: 2px dotted slategray; - color: slategray; + border-top: 2px solid #9ec5fe; + color: #9ec5fe; overflow: visible; text-align: center; height: 5px; @@ -222,9 +75,12 @@ body > h2 { padding-left: 0.25eM; } -nav { - height: fit-content; - margin: 0 0.25em; - border-left: 0.15em solid slategray; - border-right: 0.15em solid slategray; +@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 306a01d..50df871 100644 --- a/static_files/vote_chart.js +++ b/static_files/vote_chart.js @@ -1,67 +1,51 @@ -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() { - let current_url = new URL(document.URL) - current_url.hash = '' // Strip the local part of the URL, for example if we select a Truth - const vote_response = await fetch(current_url.toString()+"/votes"); + const vote_response = await fetch(document.URL+"/votes"); if (!vote_response.ok) { return; } - let keys = [] + const keys = ["Vérité 1", "Vérité 2", "Vérité 3", "Vérité 4", "Vérité 5", "Vérité 6", "Vérité 7"] let datasets = [] + try { - const vote_data = (await vote_response.json()); - for (let player in vote_data.votes) { + const vote_data = (await vote_response.json()).votes; + for (let player in vote_data) { datasets.push({ parsing: true, label: player, - data: vote_data.votes[player], + data: vote_data[player], }) } - for (let i = 1; i <= vote_data.truth_count; i++) { - keys.push("Vérité " + i) - } + console.log(datasets) } catch (error) { console.error("Failed to parse vote data : \n\t" + error.message); 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() } @@ -83,13 +67,6 @@ async function main() { y: { stacked: true } - }, - plugins: { - title: { - display: true, - position: 'bottom', - text: 'Répartition des suppositions' - } } } }) @@ -99,7 +76,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/base_page.html.tera b/templates/base_page.html.tera deleted file mode 100644 index c598fa8..0000000 --- a/templates/base_page.html.tera +++ /dev/null @@ -1,50 +0,0 @@ - - -{% set title = "Vérités Nova Borealis" %} - - - {{ title }} - - - {% block scripts %} - {% endblock %} - - - -{% block top_bar %} -
-
-

{{ title }}

- -
-
- {% block top_bar_side %} -
- {% if user.logged_in == true %} - - Connecté en tant que {{ user.name }} - - - {% else %} - - {% endif %} -
- {% endblock %} -
-
-{% endblock %} - -{% block body %} -{% endblock %} - - - diff --git a/templates/editable_truth.tera b/templates/editable_truth.tera new file mode 100644 index 0000000..0b94cb6 --- /dev/null +++ b/templates/editable_truth.tera @@ -0,0 +1,8 @@ +
+

Vérité {{ truth.number }}

+

{{ truth.rendered_text | safe }}

+
+
+ {% include "truth_editor" %} +
+
diff --git a/templates/index.html.tera b/templates/index.html.tera new file mode 100644 index 0000000..851acce --- /dev/null +++ b/templates/index.html.tera @@ -0,0 +1,95 @@ + + +{% set title = "Vérités Fabula Ultima" %} + + + {{ title }} + + {% if user.logged_in == true and not user.is_admin %} + + {% endif %} + + + + +{# 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 has_vote = false %} +{% endif -%} + + +
+

{{ title }}

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

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

+ {% else %} + + {% endif %} +
+ +

Semaine {{ week_number }}

+
+
+ {% if user.logged_in == true and user.is_admin == false %} +
+ {% endif %} + + {# Truths start at 1 but the array starts at 0 #} + {% set index_delta = 1 %} + {% for truth in truths %} + {# + The truths are in an ordered array, but one of them might be the user's. + In this case, we need to stop the array index from incrementing if the current + truth is the user's, as they cannot have voted for themselves, leading to one + less votes than there are truths. + #} + {%- if truth.author_id == user.id -%} + {%- set_global index_delta = 2 -%} + {% endif %} + {% set truth_index = truth.number - index_delta %} + + {% if user.is_admin == true %} + {% include "editable_truth" %} + {% else %} + {% include "truth" %} + {% endif %} + {% endfor %} + + {% if user.logged_in == true and user.is_admin == false %} +
+ +
+ {% endif %} + + {# If admin, show an additional box for creating a new Truth. #} + {% if user.is_admin == true %} +
+

Nouvelle vérité

+
+ {% include "truth_editor" %} +
+
+ {% endif %} +
+ +
+
+ +
+
+
+ + + diff --git a/templates/tags/filtered_truths.html.tera b/templates/tags/filtered_truths.html.tera deleted file mode 100644 index cb94d54..0000000 --- a/templates/tags/filtered_truths.html.tera +++ /dev/null @@ -1,28 +0,0 @@ -{% extends "base_page" %} - -{% block top_bar_side %} -{% endblock %} - -{% block body %} - {% if tags | length == 0 %} -

Ensemble des vérités à ce jour

- {% elif tags | length == 1 %} -

Vérités correponsdantes au thème

- {% else %} -

Vérités correponsdantes aux thèmes

- {% endif %} - -
- {% for tag in tags %} - {{ tag.name }} - {% endfor %} -
- - {% for truth in truths %} -
-

Semaine {{ truth.week }} - Vérité {{ truth.number }}

-

{{ truth.rendered_text | safe }}

-
- {% endfor %} -{% endblock %} diff --git a/templates/tags/index.html.tera b/templates/tags/index.html.tera deleted file mode 100644 index 5e423d4..0000000 --- a/templates/tags/index.html.tera +++ /dev/null @@ -1,42 +0,0 @@ -{% extends "base_page" %} - -{% block top_bar_side %} -{% endblock %} - -{% block body %} -

Liste des thèmes de Vérité

- -
- {% for tag in tags %} -
- -
- {% endfor %} -
- - - {% if user.is_admin %} -
- -

Éditer les thèmes

- -
- {% for tag in tags %} -
- - - - -
- {% endfor %} -
- -
-
- - -
- -
- {% endif %} -{% endblock %} diff --git a/templates/truth.html.tera b/templates/truth.html.tera new file mode 100644 index 0000000..c54388b --- /dev/null +++ b/templates/truth.html.tera @@ -0,0 +1,26 @@ +
+

Vérité {{ truth.number }}

+

{{ truth.rendered_text | safe }}

+ {% if user.logged_in %} +
+ + {% endif %} +
diff --git a/templates/weeks/truth_editor.html.tera b/templates/truth_editor.html.tera similarity index 93% rename from templates/weeks/truth_editor.html.tera rename to templates/truth_editor.html.tera index 615ed3e..894dac4 100644 --- a/templates/weeks/truth_editor.html.tera +++ b/templates/truth_editor.html.tera @@ -1,4 +1,4 @@ - - - - {% endif %} - - {% if user.logged_in == true and user.is_admin == false and not lock_truth_form %} -
- {% endif %} - - {# Truths start at 1 but the array starts at 0 #} - {% set index_delta = 1 %} - {% for truth in truths %} - {# - The truths are in an ordered array, but one of them might be the user's. - In this case, we need to stop the array index from incrementing if the current - truth is the user's, as they cannot have voted for themselves, leading to one - less votes than there are truths. - #} - {%- if truth.author_id == user.id -%} - {%- set_global index_delta = 2 -%} - {% endif %} - {% set truth_index = truth.number - index_delta %} - - {% if user.is_admin == true %} - {% include "weeks/editable_truth" %} - {% else %} - {% include "weeks/truth" %} - {% endif %} - {% endfor %} - - {% if user.logged_in == true and user.is_admin == false and not lock_truth_form %} -
- -
- {% endif %} - - {# If admin, show an additional box for creating a new Truth. #} - {% if user.is_admin == true %} -
-

Nouvelle vérité

-
- {% include "weeks/truth_editor" %} -
-
- {% endif %} - - -
-
- -
-
- -{% endblock %} diff --git a/templates/weeks/truth.html.tera b/templates/weeks/truth.html.tera deleted file mode 100644 index 5eaa580..0000000 --- a/templates/weeks/truth.html.tera +++ /dev/null @@ -1,40 +0,0 @@ -{%- 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 vote_data.votes | filter(attribute="truth_id", value=truth.id) -%} - {%- set is_disabled = "disabled" -%} -{%- endif -%} - -
-

Vérité {{ truth.number }}

-

{{ truth.rendered_text | safe }}

-
-
- {% for tag in truth.tags %} - {{ tag.name }} - {% endfor %} -
- {% if user.logged_in %} -
- - {% endif %} -