Skip to content

Commit

Permalink
Personal statistics
Browse files Browse the repository at this point in the history
  • Loading branch information
Leonid Kozarin committed Jul 1, 2024
1 parent 88dac4c commit 5d896df
Show file tree
Hide file tree
Showing 14 changed files with 197 additions and 12 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,11 @@ commands:
same_person: "You cannot fight with yourself!"
battle_already_in_progress: "The fight is in progress already! The message will be updated in a moment…"
stats:
description: "Win statistics"
description: "Statistics"
length: "Length: <b>%{length}</b>\nPosition in the top: <b>%{pos}</b>"
pvp: "Win rate: <b>%{win_rate}</b>.\nFights: <b>%{battles}</b>.\nWins: <b>%{wins}</b>.\nMax win streak: <b>%{win_streak}</b>.\nAcquired length: <b>%{acquired} cm</b>.\nLost length: <b>%{lost} cm</b>."
notice: "The collection of statistics started on July 2, 2024."
personal: "<i>Your personal statistics:</i>\n— Number of the chats in which you play: <b>%{chats}</b>.\n— Maximum length: <b>%{max_length}</b>.\n— Sum of dicks across all the chats: <b>%{total_length}</b>."
loan:
description: "Minus? Take a loan!"
debt: "Left to pay <b>%{debt} cm</b>"
Expand Down
3 changes: 2 additions & 1 deletion locales/ru.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,11 @@ commands:
same_person: "Нельзя биться с самим собой!"
battle_already_in_progress: "Сражение уже началось! Сообщение обновится через мгновение…"
stats:
description: "Статистика побед"
description: "Статистика"
length: "Длина: <b>%{length}</b>\nПозиция в топе: <b>%{pos}</b>"
pvp: "Процент выигрышей: <b>%{win_rate}</b>.\nСыгранных боёв: <b>%{battles}</b>.\nПобед: <b>%{wins}</b>.\nМаксимум побед подряд: <b>%{win_streak}</b>.\nВыиграно: <b>%{acquired} см</b>.\nПроиграно: <b>%{lost} см</b>."
notice: "Статистика начала собираться со 2 июля 2024."
personal: "<i>Персональная статистика:</i>\n— Количество чатов: <b>%{chats}</b>.\n— Максимальная длина: <b>%{max_length}</b>.\n— Сумма писюнов среди всех чатов: <b>%{total_length}</b>."
loan:
description: "Минус? Возьми кредит!"
debt: "Осталось выплатить <b>%{debt} см</b>"
Expand Down
1 change: 1 addition & 0 deletions src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub async fn set_my_commands(bot: &Bot, lang_code: &str, toggles: &CachedEnvTogg
let personal_commands = vec![
HelpCommands::bot_commands(),
PromoCommands::bot_commands(),
StatsCommands::bot_commands(),
];
let group_commands = vec![
HelpCommands::bot_commands(),
Expand Down
2 changes: 1 addition & 1 deletion src/handlers/inline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ impl InlineCommand {
},
InlineCommand::Stats => {
metrics::CMD_STATS.inline.inc();
stats::stats_impl(repos, from_refs, config.features.pvp)
stats::chat_stats_impl(repos, from_refs, config.features.pvp)
.await
.map(InlineResult::text)
},
Expand Down
16 changes: 14 additions & 2 deletions src/handlers/stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ pub async fn cmd_handler(bot: Bot, msg: Message, repos: repo::Repositories, app_
let from = msg.from().ok_or(anyhow!("unexpected absence of a FROM field"))?;
let chat_id = msg.chat.id.into();
let from_refs = FromRefs(from, &chat_id);
let answer = stats_impl(&repos, from_refs, features).await?;

let answer = if msg.chat.is_private() {
personal_stats_impl(&repos, from_refs).await?
} else {
chat_stats_impl(&repos, from_refs, features).await?
};

reply_html(bot, msg, answer).await?;
} else {
Expand All @@ -32,7 +37,14 @@ pub async fn cmd_handler(bot: Bot, msg: Message, repos: repo::Repositories, app_
Ok(())
}

pub(crate) async fn stats_impl(repos: &repo::Repositories, from_refs: FromRefs<'_>, features: BattlesFeatureToggles) -> anyhow::Result<String> {
async fn personal_stats_impl(repos: &repo::Repositories, from_refs: FromRefs<'_>) -> anyhow::Result<String> {
let lang_code = ensure_lang_code(Some(from_refs.0));
repos.personal_stats.get(from_refs.0.id).await
.map(|stats| t!("commands.stats.personal", locale = &lang_code,
chats = stats.chats, max_length = stats.max_length, total_length = stats.total_length))
}

pub(crate) async fn chat_stats_impl(repos: &repo::Repositories, from_refs: FromRefs<'_>, features: BattlesFeatureToggles) -> anyhow::Result<String> {
let lang_code = ensure_lang_code(Some(from_refs.0));
let (length, position) = repos.dicks.fetch_dick(from_refs.0.id, &from_refs.1.kind()).await?
.map(|dick| (dick.length, dick.position.unwrap_or_default()))
Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
.branch(Update::filter_message().filter_command::<DickOfDayCommands>().filter(checks::is_group_chat).endpoint(handlers::dod_cmd_handler))
.branch(Update::filter_message().filter_command::<BattleCommands>().filter(checks::is_group_chat).endpoint(handlers::pvp::cmd_handler))
.branch(Update::filter_message().filter_command::<BattleCommandsNoArgs>().filter(checks::is_group_chat).endpoint(handlers::pvp::cmd_handler_no_args))
.branch(Update::filter_message().filter_command::<StatsCommands>().filter(checks::is_group_chat).endpoint(handlers::stats::cmd_handler))
.branch(Update::filter_message().filter_command::<StatsCommands>().endpoint(handlers::stats::cmd_handler))
.branch(Update::filter_message().filter_command::<LoanCommands>().filter(checks::is_group_chat).endpoint(handlers::loan::cmd_handler))
.branch(Update::filter_message().filter_command::<ImportCommands>().filter(checks::is_group_chat).endpoint(handlers::import_cmd_handler))
.branch(Update::filter_message().filter_command::<PromoCommands>().filter(checks::is_not_group_chat).endpoint(handlers::promo_cmd_handler))
Expand Down
6 changes: 5 additions & 1 deletion src/repo/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod pvpstats;

#[cfg(test)]
pub(crate) mod test;
mod stats;

use anyhow::anyhow;
use sqlx::{Pool, Postgres};
Expand All @@ -20,6 +21,7 @@ pub use import::*;
pub use promo::*;
pub use loans::*;
pub use pvpstats::*;
pub use stats::*;
use crate::config;
use crate::config::DatabaseConfig;

Expand All @@ -32,6 +34,7 @@ pub struct Repositories {
pub promo: Promo,
pub loans: Loans,
pub pvp_stats: BattleStatsRepo,
pub personal_stats: PersonalStatsRepo,
}

impl Repositories {
Expand All @@ -43,7 +46,8 @@ impl Repositories {
import: Import::new(db_conn.clone()),
promo: Promo::new(db_conn.clone()),
loans: Loans::new(db_conn.clone(), config),
pvp_stats: BattleStatsRepo::new(db_conn.clone(), config.features)
pvp_stats: BattleStatsRepo::new(db_conn.clone(), config.features),
personal_stats: PersonalStatsRepo::new(db_conn.clone()),
}
}
}
Expand Down
42 changes: 42 additions & 0 deletions src/repo/stats.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use num_traits::ToPrimitive;
use sqlx::FromRow;
use teloxide::types::UserId;
use crate::repository;

#[derive(FromRow)]
struct PersonalStatsEntity {
chats: Option<i64>,
max_length: Option<i32>,
total_length: Option<i64>,
}

pub struct PersonalStats {
pub chats: u64,
pub max_length: u32,
pub total_length: u64,
}

impl From<PersonalStatsEntity> for PersonalStats {
fn from(value: PersonalStatsEntity) -> Self {
Self {
chats: value.chats.map(|x| x.to_u64().expect("chats count, fetched from the database, must fit into u64")).unwrap_or_default(),
max_length: value.max_length.map(|x| x.to_u32().expect("max_length, fetched from the database, must fit into u32")).unwrap_or_default(),
total_length: value.total_length.map(|x| x.to_u64().expect("total_length, fetched from the database, must fit into u64")).unwrap_or_default(),
}
}
}

repository!(PersonalStatsRepo,
pub async fn get(&self, user_id: UserId) -> anyhow::Result<PersonalStats> {
sqlx::query_as!(PersonalStatsEntity,
r#"SELECT count(chat_id) AS chats,
max(length) AS max_length,
sum(length) AS total_length
FROM Dicks WHERE uid = $1"#,
user_id.0 as i64)
.fetch_one(&self.pool)
.await
.map(PersonalStats::from)
.map_err(Into::into)
}
);
1 change: 1 addition & 0 deletions src/repo/test/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ mod import;
mod promo;
mod loans;
mod pvpstats;
mod stats;

use std::str::FromStr;
use reqwest::Url;
Expand Down
2 changes: 1 addition & 1 deletion src/repo/test/pvpstats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ async fn test_all() {
let chat_id = ChatIdKind::ID(ChatId(CHAT_ID));
let bet = 42;

// create user and dick #2
// create user and dick #1
create_user(&db).await;
create_dick(&db).await;
let uid_1 = UserId(UID as u64);
Expand Down
36 changes: 36 additions & 0 deletions src/repo/test/stats.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use teloxide::prelude::{ChatId, UserId};
use testcontainers::clients;
use crate::repo;
use crate::repo::{ChatIdKind, ChatIdPartiality};
use crate::repo::test::{CHAT_ID, start_postgres, UID};
use crate::repo::test::dicks::create_user;

#[tokio::test]
async fn test_all() {
let docker = clients::Cli::default();
let (_container, db) = start_postgres(&docker).await;
let personal_stats = repo::PersonalStatsRepo::new(db.clone());
let dicks = repo::Dicks::new(db.clone(), Default::default());

let chat_id_1 = ChatIdKind::ID(ChatId(CHAT_ID));
let chat_id_2 = ChatIdKind::ID(ChatId(CHAT_ID + 1));
let uid = UserId(UID as u64);
create_user(&db).await;

let stats = personal_stats.get(uid).await
.expect("couldn't fetch the empty stats");
assert_eq!(stats.chats, 0);
assert_eq!(stats.max_length, 0);
assert_eq!(stats.total_length, 0);

dicks.create_or_grow(uid, &ChatIdPartiality::Specific(chat_id_1), 10).await
.expect("couldn't grow the dick in the first chat");
dicks.create_or_grow(uid, &ChatIdPartiality::Specific(chat_id_2), 20).await
.expect("couldn't grow the dick in the second chat");

let stats = personal_stats.get(uid).await
.expect("couldn't fetch the non-null stats");
assert_eq!(stats.chats, 2);
assert_eq!(stats.max_length, 20);
assert_eq!(stats.total_length, 30);
}

0 comments on commit 5d896df

Please sign in to comment.