From f09af791e261a530727dfc1605cabb196d28ca95 Mon Sep 17 00:00:00 2001 From: jmanczak Date: Sun, 5 Apr 2026 23:14:49 +0200 Subject: [PATCH] fetch newest quote for dashboard, helpers --- src/quotes/mod.rs | 23 ++++++++++++- src/web/pages/dashboard.rs | 69 ++++++++++++++------------------------ 2 files changed, 48 insertions(+), 44 deletions(-) diff --git a/src/quotes/mod.rs b/src/quotes/mod.rs index 604f60c..865219b 100644 --- a/src/quotes/mod.rs +++ b/src/quotes/mod.rs @@ -1,5 +1,5 @@ use axum::{http::StatusCode, response::IntoResponse}; -use chrono::{DateTime, FixedOffset}; +use chrono::{DateTime, FixedOffset, Utc}; use rusqlite::{Connection, OptionalExtension}; use serde::Serialize; use uuid::Uuid; @@ -34,6 +34,15 @@ pub enum QuoteError { DatabaseError(#[from] DatabaseError), } +impl Quote { + pub fn get_creation_timestamp(&self) -> DateTime { + // unwrap here because all IDs use UUIDv7 + let (s, n) = self.id.get_timestamp().unwrap().to_unix(); + // unwrap here because timestamps held by UUIDs are valid by spec + DateTime::from_timestamp(s as i64, n).unwrap() + } +} + impl Quote { pub fn total_count(conn: &Connection) -> Result { Ok(conn.query_row("SELECT COUNT(*) FROM quotes", (), |r| r.get(0))?) @@ -92,6 +101,18 @@ impl Quote { public, }) } + pub fn get_newest(conn: &Connection) -> Result, QuoteError> { + let id: Option = conn + .query_row("SELECT id FROM quotes ORDER BY id DESC LIMIT 1", (), |r| { + r.get(0) + }) + .optional()?; + + match id { + Some(id) => Ok(Some(Self::get_by_id(conn, id)?)), + None => Ok(None), + } + } pub fn create( conn: &Connection, lines: Vec<(String, Name)>, diff --git a/src/web/pages/dashboard.rs b/src/web/pages/dashboard.rs index 095b0fa..7267021 100644 --- a/src/web/pages/dashboard.rs +++ b/src/web/pages/dashboard.rs @@ -24,8 +24,9 @@ const LINKS: &[(&str, &str, &str)] = &[ pub async fn page(req: Request) -> Result { let u = User::authenticate(req.headers()).ok().flatten(); - let mut conn = database::conn()?; - let tx = conn.transaction()?; + let conn = database::conn()?; + + let newest_quote = Quote::get_newest(&conn)?; Ok(base( "Dashboard | Mnemosyne", @@ -35,8 +36,15 @@ pub async fn page(req: Request) -> Result { div class="mx-auto max-w-4xl mt-4 grid grid-cols-1 sm:grid-cols-2 gap-4" { div class="flex flex-col" { p {"Newest Quote"} - p class="text-neutral-500 font-light mb-4" {"This just in! This quote was added 15s ago."} - div class="flex-1 [&>div]:h-full" {(quote(&sample_quote_1()))} + @if let Some(q) = newest_quote { + p class="text-neutral-500 font-light mb-4" { + "This just in! This quote was added " + (format_time_ago(q.get_creation_timestamp())) " ago." + } + div class="flex-1 [&>div]:h-full" {(quote(&q))} + } @else { + p class="text-neutral-500 font-light mb-4" {"No quotes yet."} + } } div class="flex flex-col" { p {"Quote of the Day"} @@ -60,25 +68,25 @@ pub async fn page(req: Request) -> Result { } div class="mx-auto max-w-4xl mt-4 flex flex-row gap-2" { (chip(html!({ - @match Quote::total_count(&tx) { + @match Quote::total_count(&conn) { Ok(count) => {(count) " QUOTES TOTAL"}, Err(_) => span class="text-red-400" {"QUOTE COUNT ERR"}, } }))) (chip(html!({ - @match Person::total_count(&tx) { + @match Person::total_count(&conn) { Ok(count) => {(count) " PERSONS TOTAL"}, Err(_) => span class="text-red-400" {"PERSON COUNT ERR"}, } }))) (chip(html!({ - @match Tag::total_count(&tx) { + @match Tag::total_count(&conn) { Ok(count) => {(count) " TAGS TOTAL"}, Err(_) => span class="text-red-400" {"TAG COUNT ERR"} } }))) (chip(html!({ - @match User::total_count(&tx) { + @match User::total_count(&conn) { Ok(count) => {(count) " USERS TOTAL"}, Err(_) => span class="text-red-400" {"USER COUNT ERR"} } @@ -90,41 +98,6 @@ pub async fn page(req: Request) -> Result { )) } -fn sample_quote_1() -> Quote { - Quote { - id: Uuid::now_v7(), - public: true, - location: Some(String::from("Poznań")), - context: Some(String::from("Wykład z językoznawstwa")), - created_by: Uuid::max(), - timestamp: DateTime::from(Utc::now()), - lines: vec![ - QuoteLine { - id: Uuid::now_v7(), - content: String::from("Nie wiem, czy są tutaj osoby fanowskie zipline-ów?"), - attribution: Name { - id: Uuid::nil(), - created_by: Uuid::max(), - person_id: Uuid::now_v7(), - is_primary: true, - name: String::from("dr. Barbara Konat"), - }, - }, - QuoteLine { - id: Uuid::now_v7(), - content: String::from("Taka uprząż co robi pziuuum!"), - attribution: Name { - id: Uuid::nil(), - created_by: Uuid::max(), - person_id: Uuid::now_v7(), - is_primary: true, - name: String::from("dr. Barbara Konat"), - }, - }, - ], - } -} - fn sample_quote_2() -> Quote { Quote { id: Uuid::now_v7(), @@ -159,3 +132,13 @@ fn sample_quote_2() -> Quote { ], } } + +fn format_time_ago(dt: DateTime) -> String { + let secs = Utc::now().signed_duration_since(dt).num_seconds(); + match secs { + ..60 => format!("{}s", secs), + 60..3600 => format!("{}m", secs / 60), + 3600..86400 => format!("{}h", secs / 3600), + _ => format!("{}d", secs / 86400), + } +}