From 504c580dfd50794063849552ee536e461f713e20 Mon Sep 17 00:00:00 2001 From: BurnyLlama Date: Sun, 16 Apr 2023 00:15:06 +0200 Subject: [PATCH] Now races and teamraces are paginated. --- src/api/races.rs | 24 ++++++--- src/api/teamraces.rs | 24 ++++++--- src/database/models/mod.rs | 1 + src/database/models/race.rs | 65 ++++++++++++++++++----- src/database/models/search_result.rs | 16 ++++++ src/database/models/teamrace.rs | 77 +++++++++++++++++++++++----- 6 files changed, 169 insertions(+), 38 deletions(-) create mode 100644 src/database/models/search_result.rs diff --git a/src/api/races.rs b/src/api/races.rs index 6bf16d6..6d398a1 100644 --- a/src/api/races.rs +++ b/src/api/races.rs @@ -1,24 +1,34 @@ use rocket::{serde::json::Json, Route, State}; -use crate::database::{models::race::Race, DatabaseHandler}; +use crate::database::{ + models::{ + race::Race, + search_result::{get_page_number_as_i32_from_str, SearchResult}, + }, + DatabaseHandler, +}; -#[get("/player/")] +#[get("/player/?")] async fn get_races_by_player( db: &State, player: &str, -) -> Result>, String> { - match Race::get_races_by_player(db, player).await { + page: Option<&str>, +) -> Result>, String> { + let page_num = get_page_number_as_i32_from_str(page); + match Race::get_races_by_player(db, player, page_num).await { Ok(maps) => Ok(Json(maps)), Err(err) => Err(format!("Error: {}", err)), } } -#[get("/map/")] +#[get("/map/?")] async fn get_races_by_map( db: &State, map: &str, -) -> Result>, String> { - match Race::get_races_by_map(db, map).await { + page: Option<&str>, +) -> Result>, String> { + let page_num = get_page_number_as_i32_from_str(page); + match Race::get_races_by_map(db, map, page_num).await { Ok(maps) => Ok(Json(maps)), Err(err) => Err(format!("Error: {}", err)), } diff --git a/src/api/teamraces.rs b/src/api/teamraces.rs index 037ea5d..449aff0 100644 --- a/src/api/teamraces.rs +++ b/src/api/teamraces.rs @@ -1,24 +1,34 @@ use rocket::{serde::json::Json, Route, State}; -use crate::database::{models::teamrace::Teamrace, DatabaseHandler}; +use crate::database::{ + models::{ + search_result::{get_page_number_as_i32_from_str, SearchResult}, + teamrace::Teamrace, + }, + DatabaseHandler, +}; -#[get("/player/")] +#[get("/player/?")] async fn get_teamrace_by_player( db: &State, player: &str, -) -> Result>, String> { - match Teamrace::get_teamrace_by_player(db, player).await { + page: Option<&str>, +) -> Result>, String> { + let page_num = get_page_number_as_i32_from_str(page); + match Teamrace::get_teamrace_by_player(db, player, page_num).await { Ok(maps) => Ok(Json(maps)), Err(err) => Err(format!("Error: {}", err)), } } -#[get("/map/")] +#[get("/map/?")] async fn get_teamrace_by_map( db: &State, map: &str, -) -> Result>, String> { - match Teamrace::get_teamrace_by_map(db, map).await { + page: Option<&str>, +) -> Result>, String> { + let page_num = get_page_number_as_i32_from_str(page); + match Teamrace::get_teamrace_by_map(db, map, page_num).await { Ok(maps) => Ok(Json(maps)), Err(err) => Err(format!("Error: {}", err)), } diff --git a/src/database/models/mod.rs b/src/database/models/mod.rs index 53f5ecf..437895b 100644 --- a/src/database/models/mod.rs +++ b/src/database/models/mod.rs @@ -1,3 +1,4 @@ pub mod map; pub mod race; +pub mod search_result; pub mod teamrace; diff --git a/src/database/models/race.rs b/src/database/models/race.rs index d4e3e21..af5ba72 100644 --- a/src/database/models/race.rs +++ b/src/database/models/race.rs @@ -3,6 +3,8 @@ use serde::{Deserialize, Serialize}; use crate::database::DatabaseHandler; +use super::search_result::{SearchResult, ROWS_PER_PAGE}; + #[derive(Debug, Clone, sqlx::FromRow, Serialize, Deserialize)] pub struct Race { pub name: String, @@ -19,8 +21,9 @@ impl Race { pub async fn get_races_by_player( db: &DatabaseHandler, player: &str, - ) -> Result, sqlx::Error> { - sqlx::query_as!( + page: i32, + ) -> Result, sqlx::Error> { + let results = sqlx::query_as!( Race, " SELECT name, @@ -42,35 +45,73 @@ impl Race { player ) .fetch_all(&db.pool) - .await + .await?; + + let total_pages = sqlx::query!( + "SELECT ceil(count(*) / $2) + 1 as total_pages FROM record_race WHERE name = $1", + player, + ROWS_PER_PAGE as i64 + ) + .fetch_one(&db.pool) + .await? + .total_pages + .map_or_else(|| 0, |as_i64| as_i64 as i32); + + Ok(SearchResult { + results, + total_pages, + current_page: page, + }) } pub async fn get_races_by_map( db: &DatabaseHandler, map: &str, - ) -> Result, sqlx::Error> { - sqlx::query_as!( + page: i32, + ) -> Result, sqlx::Error> { + let results = sqlx::query_as!( Race, " - SELECT name, + SELECT + name, map, time, timestamp, server, ARRAY[cp1, cp2, cp3, cp4, cp5, - cp6, cp7, cp8, cp9, cp10, - cp11, cp12, cp13, cp14, cp15, - cp16, cp17, cp18, cp19, cp20, - cp21, cp22, cp23, cp24, cp25 + cp6, cp7, cp8, cp9, cp10, + cp11, cp12, cp13, cp14, cp15, + cp16, cp17, cp18, cp19, cp20, + cp21, cp22, cp23, cp24, cp25 ] AS checkpoints, gameid, ddnet7 FROM record_race WHERE map = $1 ORDER BY map, time + OFFSET (($2 - 1) * $3) + FETCH NEXT $3 ROWS ONLY ", - map + map, + page, + ROWS_PER_PAGE ) .fetch_all(&db.pool) - .await + .await?; + + let total_pages = sqlx::query!( + "SELECT ceil(count(*) / $2) + 1 as total_pages FROM record_race WHERE map = $1", + map, + ROWS_PER_PAGE as i64 + ) + .fetch_one(&db.pool) + .await? + .total_pages + .map_or_else(|| 0, |as_i64| as_i64 as i32); + + Ok(SearchResult { + results, + total_pages, + current_page: page, + }) } } diff --git a/src/database/models/search_result.rs b/src/database/models/search_result.rs new file mode 100644 index 0000000..e84b78f --- /dev/null +++ b/src/database/models/search_result.rs @@ -0,0 +1,16 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct SearchResult { + pub results: Vec, + pub current_page: i32, + pub total_pages: i32, +} + +/// This constant should be used everywhere so that it can easily changed. +pub const ROWS_PER_PAGE: i32 = 200; + +/// This gets an i32 from an Option<&str>. It is very fail safe of any bad value, where it will return 1 instead of an error. +pub fn get_page_number_as_i32_from_str(page_as_str: Option<&str>) -> i32 { + str::parse(page_as_str.unwrap_or("1")).unwrap_or(1) +} diff --git a/src/database/models/teamrace.rs b/src/database/models/teamrace.rs index f260b3b..49d31f9 100644 --- a/src/database/models/teamrace.rs +++ b/src/database/models/teamrace.rs @@ -1,7 +1,9 @@ use chrono::NaiveDateTime; use serde::{Deserialize, Serialize}; -use crate::database::DatabaseHandler; +use crate::database::{models::search_result::ROWS_PER_PAGE, DatabaseHandler}; + +use super::search_result::SearchResult; #[derive(Debug, Clone, sqlx::FromRow, Serialize, Deserialize)] pub struct Teamrace { @@ -15,25 +17,76 @@ impl Teamrace { pub async fn get_teamrace_by_player( db: &DatabaseHandler, player: &str, - ) -> Result, sqlx::Error> { - sqlx::query_as!( + page: i32, + ) -> Result, sqlx::Error> { + let results = sqlx::query_as!( Teamrace, - "SELECT players, map, time, timestamp FROM record_teamrace_array WHERE $1 = ANY(players)", - player - ).fetch_all(&db.pool) - .await + " + SELECT players, map, time, timestamp + FROM record_teamrace_array + WHERE $1 = ANY(players) + OFFSET (($2 - 1) * $3) + FETCH NEXT $3 ROWS ONLY + ", + player, + page, + ROWS_PER_PAGE + ) + .fetch_all(&db.pool) + .await?; + + let total_pages = sqlx::query!( + "SELECT ceil(count(*) / $2) + 1 as total_pages FROM record_teamrace_array WHERE $1 = ANY(players)", + player, + ROWS_PER_PAGE as i64 + ) + .fetch_one(&db.pool) + .await? + .total_pages + .map_or_else(|| 0, |as_i64| as_i64 as i32); + + Ok(SearchResult { + results, + total_pages, + current_page: page, + }) } pub async fn get_teamrace_by_map( db: &DatabaseHandler, map: &str, - ) -> Result, sqlx::Error> { - sqlx::query_as!( + page: i32, + ) -> Result, sqlx::Error> { + let results = sqlx::query_as!( Teamrace, - "SELECT players, map, time, timestamp FROM record_teamrace_array WHERE map = $1", - map + " + SELECT players, map, time, timestamp + FROM record_teamrace_array + WHERE map = $1 + OFFSET (($2 - 1) * $3) + FETCH NEXT $3 ROWS ONLY + ", + map, + page, + ROWS_PER_PAGE ) .fetch_all(&db.pool) - .await + .await?; + + let total_pages = sqlx::query!( + "SELECT ceil(count(*) / $2) + 1 as total_pages FROM record_teamrace_array WHERE map = $1", + map, + ROWS_PER_PAGE as i64 + ) + .fetch_one(&db.pool) + .await? + .total_pages + .map_or_else(|| 0, |as_i64| as_i64 as i32); + + Ok(SearchResult { + results, + total_pages, + current_page: page, + }) } }