This commit is contained in:
2025-07-20 13:38:44 +02:00
commit a58df1cb8e
35 changed files with 4108 additions and 0 deletions

125
src/routes/mod.rs Normal file
View File

@@ -0,0 +1,125 @@
use axum::response::Html;
use axum::{Json, extract::State, http::StatusCode};
use futures_util::stream::TryStreamExt;
use mongodb::Database;
use mongodb::bson::doc;
use mongodb::bson::oid::ObjectId;
use serde::{Deserialize, Serialize};
use crate::models::Character;
use crate::utils::calculate_elo;
#[derive(Deserialize)]
pub struct RateRequest {
winner_id: String,
loser_id: String,
}
#[derive(Deserialize, Serialize)]
pub struct RateResponse {
winner: Character,
loser: Character,
}
static K_FACTOR: f64 = 32.0; // K-factor for Elo rating system
pub async fn get_characters(
State(db): State<Database>,
) -> Result<Json<Vec<Character>>, StatusCode> {
let collection = db.collection::<Character>("characters");
let pipeline = vec![doc! { "$sort": { "elo_rating": -1 } }];
let mut cursor = collection
.aggregate(pipeline)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let mut characters = Vec::new();
while let Some(result) = cursor
.try_next()
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
{
let character: Character =
mongodb::bson::from_document(result).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
characters.push(character);
}
tracing::info!(count = characters.len(), "Fetched characters from DB");
Ok(Json(characters))
}
#[axum::debug_handler]
pub async fn rate(
State(db): State<Database>,
Json(req): Json<RateRequest>,
) -> Result<Json<RateResponse>, StatusCode> {
let collection = db.collection::<Character>("characters");
let winner_oid = ObjectId::parse_str(&req.winner_id).map_err(|_| StatusCode::BAD_REQUEST)?;
let loser_oid = ObjectId::parse_str(&req.loser_id).map_err(|_| StatusCode::BAD_REQUEST)?;
let winner = collection
.find_one(doc! { "_id": winner_oid })
.await
.ok()
.flatten()
.ok_or(StatusCode::NOT_FOUND)?;
let loser = collection
.find_one(doc! { "_id": loser_oid })
.await
.ok()
.flatten()
.ok_or(StatusCode::NOT_FOUND)?;
let (new_winner_elo, new_loser_elo) =
calculate_elo(winner.elo_rating, loser.elo_rating, K_FACTOR);
collection
.update_one(
doc! {"_id": &winner_oid},
doc! { "$set": { "elo_rating": new_winner_elo } },
)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
collection
.update_one(
doc! {"_id": &loser_oid},
doc! { "$set": { "elo_rating": new_loser_elo } },
)
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
// return resposnse with characters' new Elo ratings
let updated_winner = collection
.find_one(doc! { "_id": winner_oid })
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
.ok_or(StatusCode::NOT_FOUND)?;
let updated_loser = collection
.find_one(doc! { "_id": loser_oid })
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
.ok_or(StatusCode::NOT_FOUND)?;
tracing::info!(
"Rated characters: winner_id = {}, loser_id = {}, new_winner_elo =
{}, new_loser_elo = {}",
req.winner_id,
req.loser_id,
new_winner_elo,
new_loser_elo
);
Ok(Json(RateResponse {
winner: updated_winner,
loser: updated_loser,
}))
}
pub async fn index() -> Html<&'static str> {
tracing::info!("Serving index page");
Html("<h1>Welcome to the Rick and Morty Character Rating API</h1>")
}