use axum::http::StatusCode; use axum::{Extension, Json}; use axum::extract::Path; use diesel::{Connection, PgConnection}; use serde::Deserialize; use skillratings::weng_lin::WengLinConfig; use teloxide::Bot; use teloxide::requests::Requester; use teloxide::types::{ChatId, MessageId, ParseMode, ThreadId}; use crate::config; use crate::database::model::{Match, MatchI, Outcome, Player, WengLinRating}; #[derive(Debug, Clone, Deserialize)] pub struct MatchII { name: Option, player_a: i32, player_b: i32, outcome: Outcome, } #[axum::debug_handler] pub async fn get_all() -> Result>, StatusCode> { let mut conn = PgConnection::establish(config::DATABASE_URL()) .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; let matches = Match::all(&mut conn) .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; Ok(Json(matches)) } #[axum::debug_handler] pub async fn get_played_by_id( Path(player_id): Path, ) -> Result>, StatusCode> { let mut conn = PgConnection::establish(config::DATABASE_URL()) .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; let matches = Match::played_by(&mut conn, player_id) .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; Ok(Json(matches)) } fn player_to_text(player: &Player, before: &WengLinRating, after: &WengLinRating) -> String { let name = &player.username; let competitive = &player.competitive; let telegram_id = &player.telegram_id.clone().map(|t| t.0); match competitive { false => { match telegram_id { None => format!(r#"{name}"#), Some(telegram_id) => format!(r#"{name}"#), } }, true => { let before = before.human_score(); let after = after.human_score(); let change = after - before; match telegram_id { None => format!(r#"{name} ({change:+} ★)"#), Some(telegram_id) => format!(r#"{name} ({change:+} ★)"#), } }, } } fn match_to_text(r#match: &Match, player_a: &Player, player_b: &Player) -> String { let player_a = player_to_text(player_a, &r#match.player_a_wenglin_before, &r#match.player_a_wenglin_after); let player_b = player_to_text(player_b, &r#match.player_b_wenglin_before, &r#match.player_b_wenglin_after); match r#match.outcome { Outcome::AWins => match &r#match.name { Some(name) => format!("🔵 {player_a} ha trionfato su {player_b} in {name}!"), None => format!("🔵 {player_a} ha trionfato su {player_b}!"), }, Outcome::BWins => match &r#match.name { Some(name) => format!("🟠 {player_a} è stato sconfitto da {player_b} in {name}!"), None => format!("🟠 {player_a} è stato sconfitto da {player_b}!"), }, Outcome::Tie => match &r#match.name { Some(name) => format!("⚪️ {player_a} e {player_b} hanno pareggiato in {name}!"), None => format!("⚪️ {player_a} e {player_b} hanno pareggiato!"), }, } } pub async fn post_match( Extension(bot): Extension, Json(matchii): Json, ) -> Result, StatusCode> { log::debug!("New MatchII just dropped: {matchii:#?}"); let name = matchii.name; let outcome = matchii.outcome; log::trace!("Establishing database connection..."); let mut conn = PgConnection::establish(config::DATABASE_URL()) .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; log::trace!("Finding player A's info..."); let player_a = Player::get_by_id(&mut conn, matchii.player_a) .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? .ok_or_else(|| StatusCode::NOT_FOUND)?; let player_a_id = player_a.id; let player_a_wenglin_before = player_a.wenglin.clone(); log::trace!("Finding player B's info..."); let player_b = Player::get_by_id(&mut conn, matchii.player_b) .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? .ok_or_else(|| StatusCode::NOT_FOUND)?; let player_b_id = player_b.id; let player_b_wenglin_before = player_b.wenglin.clone(); log::trace!("Calculating rating changes..."); let (player_a_wenglin_after, player_b_wenglin_after) = skillratings::weng_lin::weng_lin( &player_a_wenglin_before.0, &player_b_wenglin_before.0, &outcome.into(), &WengLinConfig::default(), ); let player_a_wenglin_after = WengLinRating(player_a_wenglin_after); log::trace!("A's new rating is: {player_a_wenglin_after:?}"); let player_b_wenglin_after = WengLinRating(player_b_wenglin_after); log::trace!("B's new rating is: {player_b_wenglin_after:?}"); log::trace!("Starting database transaction..."); let (r#match, player_a, player_b) = conn.transaction(|tx| { log::trace!("Updating A's rating..."); let player_a = player_a.update_wenglin(tx, &player_a_wenglin_after) .map_err(|_| diesel::result::Error::RollbackTransaction)?; log::trace!("Updating B's rating..."); let player_b = player_b.update_wenglin(tx, &player_b_wenglin_after) .map_err(|_| diesel::result::Error::RollbackTransaction)?; log::trace!("Inserting match..."); let matchi = MatchI { name, player_a_id, player_a_wenglin_before, player_a_wenglin_after, player_b_id, player_b_wenglin_before, player_b_wenglin_after, outcome, }; let r#match = matchi.insert(tx) .map_err(|_| diesel::result::Error::RollbackTransaction)?; Ok::<(Match, Player, Player), anyhow::Error>((r#match, player_a, player_b)) }) .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; log::trace!("Preparing send message future..."); let chat = config::TELEGRAM_NOTIFICATION_CHAT_ID(); let chat = ChatId(*chat); let mut send_message_future = bot.send_message(chat, match_to_text(&r#match, &player_a, &player_b)); send_message_future.parse_mode = Some(ParseMode::Html); let topic = config::TELEGRAM_NOTIFICATION_TOPIC_ID(); if let Some(topic) = topic { let topic = MessageId(*topic); let topic = ThreadId(topic); send_message_future.message_thread_id = Some(topic); } log::trace!("Sending message..."); let _message = send_message_future.await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?; Ok(Json(r#match)) }