diff --git a/Cargo.lock b/Cargo.lock index e8a7970..cd2600c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -963,6 +963,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-range-header" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c" + [[package]] name = "httparse" version = "1.10.1" @@ -1396,6 +1402,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1936,8 +1952,10 @@ dependencies = [ "crossterm 0.29.0", "mpris", "ratatui 0.30.0", + "serde", "sqlx", "tokio", + "tower-http", "tui-textarea", ] @@ -2779,6 +2797,19 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "tower" version = "0.5.2" @@ -2795,6 +2826,32 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags 2.10.0", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "http-range-header", + "httpdate", + "mime", + "mime_guess", + "percent-encoding", + "pin-project-lite", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + [[package]] name = "tower-layer" version = "0.3.3" @@ -2862,6 +2919,12 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + [[package]] name = "unicode-bidi" version = "0.3.18" diff --git a/Cargo.toml b/Cargo.toml index 1ffe813..091666d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,8 @@ sqlx = { version = "0.8.3", features = ["sqlite", "runtime-tokio"] } tokio = { version = "1.36.0", features = ["rt", "macros", "rt-multi-thread"] } tui-textarea = "0.7.0" axum = "0.8.3" +tower-http = { version = "0.6.8", features = ["fs"] } +serde = { version = "1.0", features = ["derive"] } [profile.optimize] inherits = "release" diff --git a/src/database.rs b/src/database.rs index 79423f2..9e22b1e 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,13 +1,20 @@ -use std::i64; - use anyhow::Result; +use serde::Serialize; use sqlx::{Row, SqlitePool}; +use std::i64; #[derive(Clone)] pub struct Database { pool: SqlitePool, } +#[derive(Serialize)] +pub struct UserStats { + pub name: String, + pub rating_count: i64, + pub average_rating: f64, +} + impl Database { pub async fn new() -> Result { let pool = SqlitePool::connect("sqlite://ratings.db?mode=rwc").await?; @@ -218,7 +225,7 @@ impl Database { Ok(()) } - pub async fn get_user_most_ratings(&self) -> Result<()> { + pub async fn get_user_most_ratings(&self) -> Result> { let mut conn = self.pool.acquire().await?; let records = sqlx::query( @@ -233,16 +240,16 @@ impl Database { .fetch_all(&mut *conn) .await?; - //let id = record.try_get("id")?; - + // Map the database records into our new struct + let mut stats = Vec::new(); for r in records { - let name: &str = r.try_get("name")?; - let count: i64 = r.try_get("c")?; - let avg: f64 = r.try_get("avg(rating)")?; - - println!("Name: {name}, Ratings: {count}, Average: {avg}"); + stats.push(UserStats { + name: r.try_get("name")?, + rating_count: r.try_get("c")?, + average_rating: r.try_get("avg(rating)")?, + }); } - Ok(()) + Ok(stats) } } diff --git a/src/http_server.rs b/src/http_server.rs index 729cbcb..6b10065 100644 --- a/src/http_server.rs +++ b/src/http_server.rs @@ -8,9 +8,10 @@ use axum::{ http::StatusCode, response::{IntoResponse, Response}, routing::get, - Router, + Json, Router, }; use tokio::sync::watch::Sender; +use tower_http::services::ServeDir; use crate::database::Database; @@ -31,6 +32,8 @@ pub async fn http_serve(database: &Database, mpris_producer: Sender<(String, Str }; let app = Router::new() + .fallback_service(ServeDir::new("static")) + .route("/stats", get(get_stats)) .route("/", get(root)) .route("/rating/:rating", get(cache_rating_only)) .route("/userid/:user_id", get(add_userid)) @@ -127,3 +130,17 @@ async fn add_userid_by_card( } } } + +async fn get_stats(State(shared): State) -> Response { + match shared.database.get_user_most_ratings().await { + Ok(stats) => { + // axum::Json automatically serializes the Vec into a JSON array + // and sets the correct "Content-Type: application/json" headers. + (StatusCode::OK, Json(stats)).into_response() + } + Err(e) => { + eprintln!("Database error: {e}"); + (StatusCode::INTERNAL_SERVER_ERROR, "Failed to fetch stats").into_response() + } + } +}