diff --git a/src/web/components/quote.rs b/src/web/components/quote.rs index 2ca5166..970b425 100644 --- a/src/web/components/quote.rs +++ b/src/web/components/quote.rs @@ -4,7 +4,7 @@ use crate::{quotes::Quote, web::icons}; pub fn quote(quote: &Quote) -> Markup { html!( - div class="border border-neutral-200/25 bg-neutral-200/5 p-3 pb-1 overflow-clip rounded-md relative flex flex-col" { + div class="border border-neutral-200/25 bg-neutral-200/5 p-3 pb-1 overflow-clip rounded-md relative flex flex-col transition-colors group-hover/a:border-neutral-200/35 group-hover/a:bg-neutral-200/10" { div class="absolute top-4 right-6 -rotate-12 opacity-[.025] scale-x-[4.5] scale-y-[4]" { (PreEscaped(icons::QUOTE)) } diff --git a/src/web/pages/dashboard.rs b/src/web/pages/dashboard.rs index 1a094eb..c27b25e 100644 --- a/src/web/pages/dashboard.rs +++ b/src/web/pages/dashboard.rs @@ -61,7 +61,9 @@ pub async fn page( "This just in! This quote was added " (format_time_ago(q.get_creation_timestamp())) " ago." } - div class="flex-1 [&>div]:h-full" {(quote(q))} + div class="flex-1 [&>div]:h-full" { + a href=(format!("/quotes/{}", q.id)) class="group/a" {(quote(q))} + } } @else { p class="text-neutral-500 font-light mb-4" {"No quotes yet."} } @@ -76,7 +78,9 @@ pub async fn page( "This quote was added " (format_time_ago(q.get_creation_timestamp())) " ago." } - div class="flex-1 [&>div]:h-full" {(quote(&q))} + div class="flex-1 [&>div]:h-full" { + a href=(format!("/quotes/{}", q.id)) class="group/a" {(quote(&q))} + } } } } diff --git a/src/web/pages/quotes.rs b/src/web/pages/quotes.rs index bc11e3a..d8430d6 100644 --- a/src/web/pages/quotes.rs +++ b/src/web/pages/quotes.rs @@ -86,7 +86,7 @@ pub async fn page( } div class="flex flex-col gap-4 mb-8" { @for q in "es { - a href=(format!("/quotes/{}", q.id)) {(quote(q))} + a href=(format!("/quotes/{}", q.id)) class="group/a" {(quote(q))} } div class="flex justify-between items-center mt-4 text-neutral-400" { diff --git a/src/web/pages/quotes/id.rs b/src/web/pages/quotes/id.rs index f2a33c1..9a123e8 100644 --- a/src/web/pages/quotes/id.rs +++ b/src/web/pages/quotes/id.rs @@ -3,6 +3,7 @@ use axum::{ http::HeaderMap, response::{IntoResponse, Redirect, Response}, }; +use http::StatusCode; use maud::{PreEscaped, html}; use uuid::Uuid; @@ -14,6 +15,7 @@ use crate::{ users::{ User, auth::{UserAuthRequired, UserAuthenticate}, + permissions::Permission, }, web::{ components::{nav::nav, quote::quote}, @@ -33,6 +35,9 @@ pub async fn page( None => return Ok(Redirect::to(&format!("/login?r={}", req.uri().path())).into_response()), }; let q = Quote::get_by_id(&mut conn, id).await; + let can_delete = u + .has_permission(&mut conn, Permission::DeleteQuotes) + .await?; Ok(base( "Add Quote | Mnemosyne", @@ -53,9 +58,11 @@ pub async fn page( span class="scale-[.75]" {(PreEscaped(icons::PEN))} "Edit" } - a href=(format!("/quotes/{id}/delete")) class="px-2 py-1 cursor-pointer border rounded flex flex-row gap-1 bg-pink-400/10 border-pink-400/25 hover:bg-pink-400/20 hover:border-pink-400/45" { - span class="scale-[.75]" {(PreEscaped(icons::TRASH))} - "Delete" + @if can_delete { + a href=(format!("/quotes/{id}/delete")) class="px-2 py-1 cursor-pointer border rounded flex flex-row gap-1 bg-pink-400/10 border-pink-400/25 hover:bg-pink-400/20 hover:border-pink-400/45" { + span class="scale-[.75]" {(PreEscaped(icons::TRASH))} + "Delete" + } } } } @else { @@ -126,6 +133,9 @@ pub async fn delete( ) -> Result { let mut tx = state.pool.begin().await?; let u = User::authenticate(&mut *tx, &headers).await?.required()?; + if !u.has_permission(&mut tx, Permission::DeleteQuotes).await? { + return Ok((StatusCode::FORBIDDEN, "No permission.").into_response()); + } let q = Quote::get_by_id(&mut *tx, id).await?; LogEntry::new(&mut *tx, u, LogAction::DeleteQuote { quote: q.clone() }).await?; diff --git a/src/web/pages/users.rs b/src/web/pages/users.rs index a9c8ebc..9e5364b 100644 --- a/src/web/pages/users.rs +++ b/src/web/pages/users.rs @@ -3,6 +3,7 @@ use axum::{ response::{IntoResponse, Redirect, Response}, }; use maud::{PreEscaped, html}; +use uuid::Uuid; use crate::{ MnemoState, @@ -27,7 +28,14 @@ pub async fn page( Some(u) => u, None => return Ok(Redirect::to(&format!("/login?r={}", req.uri().path())).into_response()), }; - let us = User::get_all(&mut *conn).await; + let us = User::get_all(&mut *conn).await.map(|mut v| { + v.sort_by_key(|p| match p.id { + id if id == Uuid::nil() => (0, p.id), + id if id == Uuid::max() => (1, p.id), + _ => (2, p.id), + }); + v + }); let can_create_users = u .has_permission(&mut *conn, Permission::ManuallyCreateUsers) .await;