Add user stats route

This commit is contained in:
2026-03-21 22:01:58 +01:00
parent f19e443eb2
commit be438aa1af
4 changed files with 101 additions and 12 deletions

63
Cargo.lock generated
View File

@@ -963,6 +963,12 @@ dependencies = [
"pin-project-lite", "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]] [[package]]
name = "httparse" name = "httparse"
version = "1.10.1" version = "1.10.1"
@@ -1396,6 +1402,16 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 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]] [[package]]
name = "minimal-lexical" name = "minimal-lexical"
version = "0.2.1" version = "0.2.1"
@@ -1936,8 +1952,10 @@ dependencies = [
"crossterm 0.29.0", "crossterm 0.29.0",
"mpris", "mpris",
"ratatui 0.30.0", "ratatui 0.30.0",
"serde",
"sqlx", "sqlx",
"tokio", "tokio",
"tower-http",
"tui-textarea", "tui-textarea",
] ]
@@ -2779,6 +2797,19 @@ dependencies = [
"tokio", "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]] [[package]]
name = "tower" name = "tower"
version = "0.5.2" version = "0.5.2"
@@ -2795,6 +2826,32 @@ dependencies = [
"tracing", "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]] [[package]]
name = "tower-layer" name = "tower-layer"
version = "0.3.3" version = "0.3.3"
@@ -2862,6 +2919,12 @@ version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
[[package]]
name = "unicase"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
[[package]] [[package]]
name = "unicode-bidi" name = "unicode-bidi"
version = "0.3.18" version = "0.3.18"

View File

@@ -14,6 +14,8 @@ sqlx = { version = "0.8.3", features = ["sqlite", "runtime-tokio"] }
tokio = { version = "1.36.0", features = ["rt", "macros", "rt-multi-thread"] } tokio = { version = "1.36.0", features = ["rt", "macros", "rt-multi-thread"] }
tui-textarea = "0.7.0" tui-textarea = "0.7.0"
axum = "0.8.3" axum = "0.8.3"
tower-http = { version = "0.6.8", features = ["fs"] }
serde = { version = "1.0", features = ["derive"] }
[profile.optimize] [profile.optimize]
inherits = "release" inherits = "release"

View File

@@ -1,13 +1,20 @@
use std::i64;
use anyhow::Result; use anyhow::Result;
use serde::Serialize;
use sqlx::{Row, SqlitePool}; use sqlx::{Row, SqlitePool};
use std::i64;
#[derive(Clone)] #[derive(Clone)]
pub struct Database { pub struct Database {
pool: SqlitePool, pool: SqlitePool,
} }
#[derive(Serialize)]
pub struct UserStats {
pub name: String,
pub rating_count: i64,
pub average_rating: f64,
}
impl Database { impl Database {
pub async fn new() -> Result<Self> { pub async fn new() -> Result<Self> {
let pool = SqlitePool::connect("sqlite://ratings.db?mode=rwc").await?; let pool = SqlitePool::connect("sqlite://ratings.db?mode=rwc").await?;
@@ -218,7 +225,7 @@ impl Database {
Ok(()) Ok(())
} }
pub async fn get_user_most_ratings(&self) -> Result<()> { pub async fn get_user_most_ratings(&self) -> Result<Vec<UserStats>> {
let mut conn = self.pool.acquire().await?; let mut conn = self.pool.acquire().await?;
let records = sqlx::query( let records = sqlx::query(
@@ -233,16 +240,16 @@ impl Database {
.fetch_all(&mut *conn) .fetch_all(&mut *conn)
.await?; .await?;
//let id = record.try_get("id")?; // Map the database records into our new struct
let mut stats = Vec::new();
for r in records { for r in records {
let name: &str = r.try_get("name")?; stats.push(UserStats {
let count: i64 = r.try_get("c")?; name: r.try_get("name")?,
let avg: f64 = r.try_get("avg(rating)")?; rating_count: r.try_get("c")?,
average_rating: r.try_get("avg(rating)")?,
println!("Name: {name}, Ratings: {count}, Average: {avg}"); });
} }
Ok(()) Ok(stats)
} }
} }

View File

@@ -8,9 +8,10 @@ use axum::{
http::StatusCode, http::StatusCode,
response::{IntoResponse, Response}, response::{IntoResponse, Response},
routing::get, routing::get,
Router, Json, Router,
}; };
use tokio::sync::watch::Sender; use tokio::sync::watch::Sender;
use tower_http::services::ServeDir;
use crate::database::Database; use crate::database::Database;
@@ -31,6 +32,8 @@ pub async fn http_serve(database: &Database, mpris_producer: Sender<(String, Str
}; };
let app = Router::new() let app = Router::new()
.fallback_service(ServeDir::new("static"))
.route("/stats", get(get_stats))
.route("/", get(root)) .route("/", get(root))
.route("/rating/:rating", get(cache_rating_only)) .route("/rating/:rating", get(cache_rating_only))
.route("/userid/:user_id", get(add_userid)) .route("/userid/:user_id", get(add_userid))
@@ -127,3 +130,17 @@ async fn add_userid_by_card(
} }
} }
} }
async fn get_stats(State(shared): State<SharedState>) -> Response {
match shared.database.get_user_most_ratings().await {
Ok(stats) => {
// axum::Json automatically serializes the Vec<UserStats> 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()
}
}
}