From c4c5d4d39acf589358385a62431ca790943430c5 Mon Sep 17 00:00:00 2001 From: Leonid Kozarin Date: Thu, 12 Oct 2023 00:46:00 +0300 Subject: [PATCH] Fix 'already_imported' check --- locales/en.yml | 7 ++++ src/handlers/import.rs | 81 +++++++++++++++++++++++++++--------------- src/repo/imports.rs | 18 +++++----- 3 files changed, 69 insertions(+), 37 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index ef66184..9c04d98 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -10,7 +10,14 @@ commands: result: "The Dick of the Day is %{name}!\n\nHis dick has became longer for %{growth} cm and is %{length} cm long now" already_chosen: "The Dick of the Day has been already chosen for today! It's %{name}" import: + result: + template: "The following users were imported:\n\n%{imported}\n\nThe following users are not in the game yet:\n\n%{not_found}" + line: + imported: "➖ %{name} (%{length} cm)" + already_present: "➖ %{name} (%{length} cm)" + not_found: "➖ %{name}" errors: + not_admin: "This command is supposed to be used by admins only!" not_reply: "You must reply to a message sent by %{origin_bot}" title: diff --git a/src/handlers/import.rs b/src/handlers/import.rs index 4524b1e..a6f4eeb 100644 --- a/src/handlers/import.rs +++ b/src/handlers/import.rs @@ -26,13 +26,20 @@ pub enum ImportCommands { type Username = String; -pub struct OriginalUser { +struct OriginalUser { + name: String, + length: u32 +} + +struct UserInfo { + uid: UserId, name: String, length: u32 } struct ImportResult { - found: Vec, + imported: Vec, + already_present: Vec, not_found: Vec, } @@ -44,7 +51,6 @@ struct InvalidLines; enum BeforeImportCheckErrors { NotAdmin, NotReply, - AlreadyImported, Other(anyhow::Error) } @@ -62,27 +68,33 @@ struct Repositories<'a> { pub async fn import_cmd_handler(bot: Bot, msg: Message, users: repo::Users, imports: repo::Imports) -> HandlerResult { let lang_code = ensure_lang_code(msg.from()); - let repos = Repositories { - users: &users, - imports: &imports, - }; - - let answer = match check_params_and_parse_message(&bot, &msg, &repos).await { + let answer = match check_and_parse_message(&bot, &msg).await { Ok(txt) => { + let repos = Repositories { + users: &users, + imports: &imports, + }; let result = import_impl(repos, msg.chat.id, txt).await?; - let imported = result.found.into_iter() - .map(|u| t!("commands.import.result.imported", + let imported = result.imported.into_iter() + .map(|u| t!("commands.import.result.line.imported", + name = u.name, length = u.length, + locale = lang_code.as_str())) + .collect::>() + .join("\n"); + let already_present = result.already_present.into_iter() + .map(|u| t!("commands.import.result.line.already_present", name = u.name, length = u.length, locale = lang_code.as_str())) .collect::>() .join("\n"); let not_found = result.not_found.into_iter() - .map(|name| t!("commands.import.result.not_found", + .map(|name| t!("commands.import.result.line.not_found", name = name, locale = lang_code.as_str())) .collect::>() .join("\n"); - t!("commands.import.result.template", imported = imported, not_found = not_found, + t!("commands.import.result.template", imported = imported, + already_present = already_present, not_found = not_found, locale = lang_code.as_str()) }, Err(BeforeImportCheckErrors::Other(e)) => Err(e)?, @@ -92,7 +104,7 @@ pub async fn import_cmd_handler(bot: Bot, msg: Message, reply_html(bot, msg, answer).await } -async fn check_params_and_parse_message<'a>(bot: &Bot, msg: &Message, repos: &Repositories<'a>) -> Result { +async fn check_and_parse_message<'a>(bot: &Bot, msg: &Message) -> Result { let admin_ids = bot.get_chat_administrators(msg.chat.id) .await .map_err(|e| BeforeImportCheckErrors::Other(anyhow!(e)))? @@ -114,11 +126,6 @@ async fn check_params_and_parse_message<'a>(bot: &Bot, msg: &Message, repos: &Re Some(text) => text.to_owned() }; - let already_imported = repos.imports.were_dicks_already_imported(msg.chat.id).await?; - if already_imported { - return Err(BeforeImportCheckErrors::AlreadyImported) - } - Ok(text) } @@ -150,30 +157,46 @@ async fn import_impl<'a>(repos: Repositories<'a>, chat_id: ChatId, text: String) } let top: Vec = top.into_iter() - .filter_map(map_users) + .filter_map(map_user) .collect(); let (existing, not_existing): (Vec, Vec) = top.into_iter() .partition(|u| member_names.contains(&u.name)); + let existing: Vec = existing.into_iter() + .map(|u| UserInfo { + uid: members[&u.name], + name: u.name, + length: u.length + }) + .collect(); + let not_found = not_existing.into_iter() + .map(|u| u.name) + .collect(); + + let imported_uids: HashSet = repos.imports.get_imported_users(chat_id) + .await?.into_iter() + .filter_map(|u| u.uid.try_into().ok()) + .map(|uid| UserId(uid)) + .collect(); + + let (to_import, already_present): (Vec, Vec) = existing.into_iter() + .partition(|u| imported_uids.contains(&u.uid)); - let users: Vec = existing.iter() - .filter_map(|u| repo::ExternalUser::new(members[&u.name], u.length).ok()) + let users: Vec = to_import.iter() + .filter_map(|u| repo::ExternalUser::new(u.uid, u.length).ok()) .collect(); - if users.len() != existing.len() { + if users.len() != to_import.len() { return Err(anyhow!("couldn't convert integers for external users")) } - repos.imports.import(chat_id, &users).await?; - let not_found = not_existing.into_iter() - .map(|u| u.name) - .collect(); Ok(ImportResult { - found: existing, + imported: to_import, + already_present, not_found }) } -fn map_users(capture: Option) -> Option { +fn map_user(capture: Option) -> Option { let pos = capture?; if let (Some(name), Some(length)) = (pos.name("name"), pos.name("length")) { let name = name.as_str().to_owned(); diff --git a/src/repo/imports.rs b/src/repo/imports.rs index c16bd5c..8162824 100644 --- a/src/repo/imports.rs +++ b/src/repo/imports.rs @@ -3,9 +3,10 @@ use sqlx::{Postgres, Transaction}; use teloxide::types::{ChatId, UserId}; use crate::repository; +#[derive(sqlx::FromRow)] pub struct ExternalUser { - uid: i64, - length: i32 + pub uid: i64, + pub length: i32 } impl ExternalUser { @@ -18,12 +19,13 @@ impl ExternalUser { } repository!(Imports, - pub async fn were_dicks_already_imported(&self, chat_id: ChatId) -> anyhow::Result { - sqlx::query_scalar("SELECT count(*) > 0 AS exists FROM Imports WHERE chat_id = $1") - .bind(chat_id.0) - .fetch_one(&self.pool) - .await - .map_err(|e| e.into()) + pub async fn get_imported_users(&self, chat_id: ChatId) -> anyhow::Result> { + let chat_id: i64 = chat_id.0.try_into()?; + let res = sqlx::query_as::<_, ExternalUser>("SELECT uid, original_length AS length FROM Imports WHERE chat_id = $1") + .bind(chat_id) + .fetch_all(&self.pool) + .await?; + Ok(res) } , pub async fn import(&self, chat_id: ChatId, users: &Vec) -> anyhow::Result<()> {