leaderboard: paginate API results

This commit is contained in:
Patch Generator 2026-08-13 10:37:16 +00:00 committed by Jonas Rabenstein
commit 660fc1783e
2 changed files with 34 additions and 5 deletions

View file

@ -10,21 +10,39 @@ use sqlx::PgPool;
use crate::{ use crate::{
AppState, AppState,
model::{ActivityCrossing, LeaderboardGroup, LeaderboardRow}, model::{ActivityCrossing, LeaderboardGroup, LeaderboardPage, LeaderboardRow},
}; };
#[derive(Debug, Deserialize, Default)] #[derive(Debug, Deserialize, Default)]
pub struct LeaderboardQuery { pub struct LeaderboardQuery {
pub group_id: Option<i64>, pub group_id: Option<i64>,
#[serde(default)]
pub offset: i64,
#[serde(default = "default_page_size")]
pub limit: i64,
}
fn default_page_size() -> i64 {
50
} }
pub async fn api( pub async fn api(
State(state): State<AppState>, State(state): State<AppState>,
Query(query): Query<LeaderboardQuery>, Query(query): Query<LeaderboardQuery>,
) -> Result<Json<Vec<LeaderboardGroup>>, (axum::http::StatusCode, String)> { ) -> Result<Json<LeaderboardPage>, (axum::http::StatusCode, String)> {
build(&state.db, query.group_id) let offset = query.offset.max(0);
let limit = query.limit.clamp(1, 200);
build(&state.db, query.group_id, offset, limit)
.await .await
.map(Json) .map(|groups| {
let returned = groups.iter().map(|group| group.rows.len() as i64).sum::<i64>();
Json(LeaderboardPage {
groups,
next_offset: offset + returned,
has_more: returned == limit,
})
})
.map_err(|error| { .map_err(|error| {
tracing::error!(%error, "leaderboard query failed"); tracing::error!(%error, "leaderboard query failed");
( (
@ -34,7 +52,7 @@ pub async fn api(
}) })
} }
pub async fn build(db: &PgPool, group_id: Option<i64>) -> Result<Vec<LeaderboardGroup>> { pub async fn build(db: &PgPool, group_id: Option<i64>, offset: i64, limit: i64) -> Result<Vec<LeaderboardGroup>> {
let rows = sqlx::query( let rows = sqlx::query(
r#" r#"
WITH bucketed AS ( WITH bucketed AS (
@ -114,9 +132,13 @@ pub async fn build(db: &PgPool, group_id: Option<i64>) -> Result<Vec<Leaderboard
to_county, to_county,
crossing_time, crossing_time,
rank rank
LIMIT $2
OFFSET $3
"#, "#,
) )
.bind(group_id) .bind(group_id)
.bind(limit)
.bind(offset)
.fetch_all(db) .fetch_all(db)
.await?; .await?;

View file

@ -63,6 +63,13 @@ pub struct LeaderboardGroup {
pub rows: Vec<LeaderboardRow>, pub rows: Vec<LeaderboardRow>,
} }
#[derive(Debug, Serialize, Clone)]
pub struct LeaderboardPage {
pub groups: Vec<LeaderboardGroup>,
pub next_offset: i64,
pub has_more: bool,
}
#[derive(Debug, Serialize, Clone)] #[derive(Debug, Serialize, Clone)]
pub struct ActivityCrossing { pub struct ActivityCrossing {
pub crossing_time: DateTime<Utc>, pub crossing_time: DateTime<Utc>,