@@ -1,10 +1,7 @@
|
||||
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 sqlx::{Sqlite, SqlitePool, Transaction};
|
||||
|
||||
use crate::models::Character;
|
||||
use crate::utils::calculate_elo;
|
||||
@@ -15,7 +12,7 @@ pub struct RateRequest {
|
||||
loser_id: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[derive(Serialize)]
|
||||
pub struct RateResponse {
|
||||
winner: Character,
|
||||
loser: Character,
|
||||
@@ -24,94 +21,74 @@ pub struct RateResponse {
|
||||
static K_FACTOR: f64 = 32.0; // K-factor for Elo rating system
|
||||
|
||||
pub async fn get_characters(
|
||||
State(db): State<Database>,
|
||||
State(db): State<SqlitePool>,
|
||||
) -> 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");
|
||||
let characters =
|
||||
sqlx::query_as::<_, Character>("SELECT * FROM characters ORDER BY elo_rating DESC")
|
||||
.fetch_all(&db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
Ok(Json(characters))
|
||||
}
|
||||
|
||||
#[axum::debug_handler]
|
||||
pub async fn rate(
|
||||
State(db): State<Database>,
|
||||
State(db): State<SqlitePool>,
|
||||
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 })
|
||||
// 1. Fetch characters by ID
|
||||
let winner: Character = sqlx::query_as("SELECT * FROM characters WHERE id = ?")
|
||||
.bind(&req.winner_id)
|
||||
.fetch_optional(&db)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
|
||||
.ok_or(StatusCode::NOT_FOUND)?;
|
||||
let loser = collection
|
||||
.find_one(doc! { "_id": loser_oid })
|
||||
|
||||
let loser: Character = sqlx::query_as("SELECT * FROM characters WHERE id = ?")
|
||||
.bind(&req.loser_id)
|
||||
.fetch_optional(&db)
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
|
||||
.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 } },
|
||||
)
|
||||
let mut tx: Transaction<'_, Sqlite> = db
|
||||
.begin()
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
collection
|
||||
.update_one(
|
||||
doc! {"_id": &loser_oid},
|
||||
doc! { "$set": { "elo_rating": new_loser_elo } },
|
||||
)
|
||||
sqlx::query("UPDATE characters SET elo_rating = ? WHERE id = ?")
|
||||
.bind(new_winner_elo)
|
||||
.bind(&req.winner_id)
|
||||
.execute(&mut *tx)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
// return resposnse with characters' new Elo ratings
|
||||
let updated_winner = collection
|
||||
.find_one(doc! { "_id": winner_oid })
|
||||
sqlx::query("UPDATE characters SET elo_rating = ? WHERE id = ?")
|
||||
.bind(new_loser_elo)
|
||||
.bind(&req.loser_id)
|
||||
.execute(&mut *tx)
|
||||
.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)?;
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
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
|
||||
);
|
||||
tx.commit()
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
let updated_winner: Character = sqlx::query_as("SELECT * FROM characters WHERE id = ?")
|
||||
.bind(&req.winner_id)
|
||||
.fetch_one(&db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
let updated_loser: Character = sqlx::query_as("SELECT * FROM characters WHERE id = ?")
|
||||
.bind(&req.loser_id)
|
||||
.fetch_one(&db)
|
||||
.await
|
||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||
|
||||
Ok(Json(RateResponse {
|
||||
winner: updated_winner,
|
||||
|
Reference in New Issue
Block a user