1
Fork 0
holycow/holycow_backend/src/routes/matches.rs
2024-11-30 14:39:51 +01:00

159 lines
5.3 KiB
Rust

use axum::http::StatusCode;
use axum::{Extension, Json};
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};
#[axum::debug_handler]
pub async fn get_all()
-> Result<Json<Vec<Match>>, 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))
}
#[derive(Debug, Clone, Deserialize)]
pub struct MatchII {
name: Option<String>,
player_a: i32,
player_b: i32,
outcome: Outcome,
}
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;
match competitive {
false => {
format!("{name}")
},
true => {
let before = before.human_score();
let after = after.human_score();
let change = after - before;
format!(r#"<b><a href="tg://user?id={telegram_id}">{name}</a></b> <i>({change:+})</i>"#)
},
}
}
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 <b>{name}</b>!"),
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 <b>{name}</b>!"),
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 <b>{name}</b>!"),
None => format!("⚪️ {player_a} e {player_b} hanno pareggiato!"),
},
}
}
pub async fn post_match(
Extension(bot): Extension<Bot>,
Json(matchii): Json<MatchII>,
)
-> Result<Json<Match>, 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))
}