159 lines
5.3 KiB
Rust
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))
|
|
}
|