Skip to content

Commit 5124a35

Browse files
committed
add list of races for each driver
1 parent 2d7f4d2 commit 5124a35

6 files changed

Lines changed: 379 additions & 6 deletions

File tree

server/src/api.rs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,10 @@ pub fn create_api_router(race_state: SharedRaceState, db_pool: Option<PgPool>) -
316316
get(get_team_registrations),
317317
)
318318
.route("/drivers/{driver_id}", get(get_driver))
319+
.route(
320+
"/drivers/{driver_id}/race-results",
321+
get(get_driver_race_results),
322+
)
319323
.route("/drivers/{driver_id}/buy", post(buy_driver))
320324
.route("/drivers/{driver_id}/assign-car", post(assign_driver_car))
321325
.route("/cars/{car_id}", get(get_car))
@@ -722,6 +726,33 @@ async fn get_driver(
722726
Ok(success(Some(driver_response), None))
723727
}
724728

729+
// Get race results for a driver
730+
async fn get_driver_race_results(
731+
Path(driver_id): Path<String>,
732+
Query(params): Query<PaginationParams>,
733+
State(state): State<AppState>,
734+
) -> ApiResult<Json<ApiResponse<Vec<crate::database::DriverRaceResultDb>>>> {
735+
let pool = state
736+
.db_pool
737+
.as_ref()
738+
.ok_or_else(|| ApiError::InternalError("Database not available".to_string()))?;
739+
let uuid = Uuid::parse_str(&driver_id)
740+
.map_err(|_| ApiError::BadRequest(format!("Invalid driver ID format: {}", driver_id)))?;
741+
742+
// Verify driver exists
743+
let _driver = tdb::get_driver_by_id(pool, uuid)
744+
.await
745+
.map_err(|e| ApiError::InternalError(format!("Failed to fetch driver: {}", e)))?
746+
.ok_or_else(|| ApiError::NotFound(format!("Driver with ID {} not found", driver_id)))?;
747+
748+
// Get race results
749+
let results = tdb::get_race_results_by_driver(pool, uuid, params.limit, params.offset)
750+
.await
751+
.map_err(|e| ApiError::InternalError(format!("Failed to fetch race results: {}", e)))?;
752+
753+
Ok(success(Some(results), None))
754+
}
755+
725756
// Buy a driver
726757
async fn buy_driver(
727758
Path(driver_id): Path<String>,

server/src/database/models.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,3 +273,13 @@ pub struct CreateRaceResultRequest {
273273
pub laps_completed: i32,
274274
pub total_distance_km: f32,
275275
}
276+
277+
// Response DTO for driver race results with track and race information
278+
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
279+
pub struct DriverRaceResultDb {
280+
pub race_result_id: Uuid,
281+
pub race_id: Uuid, // UUID of the race, used for linking to the race page
282+
pub track_name: String,
283+
pub race_date: Option<DateTime<Utc>>, // start_datetime from race table
284+
pub final_position: i32,
285+
}

server/src/database/queries.rs

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -793,13 +793,20 @@ pub async fn list_races(
793793
status_filter: Option<Vec<&str>>,
794794
) -> Result<Vec<RaceDb>, sqlx::Error> {
795795
let query = if let Some(statuses) = status_filter {
796+
// Determine sort order: DESC for finished/canceled races (done), ASC for upcoming races
797+
let is_done = statuses
798+
.iter()
799+
.any(|s| *s == "FINISHED" || *s == "CANCELED");
800+
let order_direction = if is_done { "DESC" } else { "ASC" };
801+
796802
// Build a query with status filtering
797803
let status_placeholders: Vec<String> =
798804
(1..=statuses.len()).map(|i| format!("${}", i)).collect();
799805
let status_list = status_placeholders.join(", ");
800806
let base_query = format!(
801-
"SELECT id, track_id, laps, status::text as status, start_datetime, creator_id, description, created_at, updated_at FROM race WHERE status::text IN ({}) ORDER BY start_datetime ASC LIMIT ${} OFFSET ${}",
807+
"SELECT id, track_id, laps, status::text as status, start_datetime, creator_id, description, created_at, updated_at FROM race WHERE status::text IN ({}) ORDER BY COALESCE(start_datetime, created_at) {} LIMIT ${} OFFSET ${}",
802808
status_list,
809+
order_direction,
803810
statuses.len() + 1,
804811
statuses.len() + 2
805812
);
@@ -814,9 +821,9 @@ pub async fn list_races(
814821
.fetch_all(pool)
815822
.await?
816823
} else {
817-
// No status filter, return all races
824+
// No status filter, return all races (ascending by default)
818825
sqlx::query_as::<_, RaceDb>(
819-
"SELECT id, track_id, laps, status::text as status, start_datetime, creator_id, description, created_at, updated_at FROM race ORDER BY start_datetime ASC LIMIT $1 OFFSET $2"
826+
"SELECT id, track_id, laps, status::text as status, start_datetime, creator_id, description, created_at, updated_at FROM race ORDER BY COALESCE(start_datetime, created_at) ASC LIMIT $1 OFFSET $2"
820827
)
821828
.bind(limit)
822829
.bind(offset)
@@ -1285,6 +1292,49 @@ pub async fn get_race_result_by_race_and_car(
12851292
Ok(result)
12861293
}
12871294

1295+
pub async fn get_race_results_by_driver(
1296+
pool: &PgPool,
1297+
driver_id: Uuid,
1298+
limit: i64,
1299+
offset: i64,
1300+
) -> Result<Vec<DriverRaceResultDb>, sqlx::Error> {
1301+
let results = sqlx::query_as::<_, DriverRaceResultDb>(
1302+
r#"
1303+
SELECT
1304+
rr.id as race_result_id,
1305+
rr.race_id,
1306+
t.name as track_name,
1307+
r.start_datetime as race_date,
1308+
rr.final_position
1309+
FROM race_result rr
1310+
INNER JOIN race r ON rr.race_id = r.id
1311+
INNER JOIN track t ON r.track_id = t.id
1312+
WHERE rr.driver_id = $1
1313+
ORDER BY COALESCE(r.start_datetime, r.created_at) DESC
1314+
LIMIT $2 OFFSET $3
1315+
"#,
1316+
)
1317+
.bind(driver_id)
1318+
.bind(limit)
1319+
.bind(offset)
1320+
.fetch_all(pool)
1321+
.await?;
1322+
1323+
Ok(results)
1324+
}
1325+
1326+
pub async fn count_race_results_by_driver(
1327+
pool: &PgPool,
1328+
driver_id: Uuid,
1329+
) -> Result<i64, sqlx::Error> {
1330+
let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM race_result WHERE driver_id = $1")
1331+
.bind(driver_id)
1332+
.fetch_one(pool)
1333+
.await?;
1334+
1335+
Ok(count)
1336+
}
1337+
12881338
/// Save race results for all cars in a race
12891339
/// This function takes a snapshot of all cars and their final state
12901340
pub async fn save_race_results(

0 commit comments

Comments
 (0)