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]) }) }