diff --git a/Cargo.lock b/Cargo.lock index 7388199..6ad21c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1618,6 +1618,7 @@ dependencies = [ "lazy_static", "log", "num", + "regex", "rstest", "rust_decimal", "serde", diff --git a/Cargo.toml b/Cargo.toml index 29e0008..1603f26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ teloxide-core = "0.13.0" num = "0.4.3" itertools = "0.14.0" async-trait = "0.1" +regex = "1.11.2" [dev-dependencies] rstest = "0.26.1" diff --git a/src/commands/my_listings/keyboard.rs b/src/commands/my_listings/keyboard.rs index 1dde374..f3efd8e 100644 --- a/src/commands/my_listings/keyboard.rs +++ b/src/commands/my_listings/keyboard.rs @@ -1,8 +1,62 @@ -use crate::keyboard_buttons; +use crate::{ + db::{listing::PersistedListing, ListingDbId}, + keyboard_buttons, +}; +use regex::Regex; +use teloxide::types::InlineKeyboardButton; -keyboard_buttons! { - pub enum MyListingsButtons { - BackToMenu("⬅️ Back to Menu", "my_listings_back_to_menu"), +// keyboard_buttons! { +// pub enum MyListingsButtons { +// // SelectListing("Select Listing", "my_listings:", ListingDbId ), +// SelectListing("Select Listing", "my_listings:"), +// BackToMenu("⬅️ Back to Menu", "my_listings_back_to_menu"), +// } +// } + +pub enum MyListingsButtons { + SelectListing(ListingDbId), + NewListing, + BackToMenu, +} +impl MyListingsButtons { + pub fn listing_into_button(listing: &PersistedListing) -> InlineKeyboardButton { + InlineKeyboardButton::callback( + &listing.base.title, + Self::encode_listing_id(listing.persisted.id), + ) + } + + pub fn back_to_menu_into_button() -> InlineKeyboardButton { + InlineKeyboardButton::callback("Back to Menu", "my_listings_back_to_menu") + } + + pub fn new_listing_into_button() -> InlineKeyboardButton { + InlineKeyboardButton::callback("🛍️ New Listing", "my_listings_new_listing") + } + + fn encode_listing_id(listing_id: ListingDbId) -> String { + format!("my_listings:{listing_id}") + } + + fn decode_listing_id(value: &str) -> Option { + let re = Regex::new(r"my_listings:(\d+)").ok()?; + let caps = re.captures(value)?; + let listing_id = caps.get(1)?.as_str().parse::().ok()?; + Some(ListingDbId::new(listing_id)) + } +} + +impl TryFrom<&str> for MyListingsButtons { + type Error = anyhow::Error; + fn try_from(value: &str) -> Result { + if let Some(listing_id) = Self::decode_listing_id(value) { + return Ok(MyListingsButtons::SelectListing(listing_id)); + } + match value { + "my_listings_new_listing" => Ok(MyListingsButtons::NewListing), + "my_listings_back_to_menu" => Ok(MyListingsButtons::BackToMenu), + _ => anyhow::bail!("Unknown MyListingsButtons: {value}"), + } } } diff --git a/src/commands/my_listings/mod.rs b/src/commands/my_listings/mod.rs index 39ea684..ceaeb93 100644 --- a/src/commands/my_listings/mod.rs +++ b/src/commands/my_listings/mod.rs @@ -5,12 +5,15 @@ use crate::{ commands::{ enter_main_menu, my_listings::keyboard::{ManageListingButtons, MyListingsButtons}, - new_listing::{enter_edit_listing_draft, ListingDraft}, + new_listing::{enter_edit_listing_draft, enter_select_new_listing_type, ListingDraft}, }, db::{ listing::{ListingFields, PersistedListing}, user::PersistedUser, - ListingDAO, ListingDbId, UserDAO, + ListingDAO, ListingDbId, + }, + handler_utils::{ + find_or_create_db_user_from_callback_query, find_or_create_db_user_from_message, }, message_utils::{extract_callback_data, pluralize_with_count, send_message, MessageTarget}, Command, DialogueRootState, HandlerResult, RootDialogue, @@ -41,7 +44,6 @@ impl From for DialogueRootState { pub fn my_listings_inline_handler() -> Handler<'static, HandlerResult, DpHandlerDescription> { Update::filter_inline_query() - .inspect(|query: InlineQuery| info!("Received inline query: {:?}", query)) .filter_map_async(inline_query_extract_forward_listing) .endpoint(handle_forward_listing) } @@ -50,7 +52,9 @@ pub fn my_listings_handler() -> Handler<'static, HandlerResult, DpHandlerDescrip dptree::entry() .branch( Update::filter_message().filter_command::().branch( - dptree::case![Command::MyListings].endpoint(handle_my_listings_command_input), + dptree::case![Command::MyListings] + .filter_map_async(find_or_create_db_user_from_message) + .endpoint(handle_my_listings_command_input), ), ) .branch( @@ -60,12 +64,14 @@ pub fn my_listings_handler() -> Handler<'static, HandlerResult, DpHandlerDescrip case![DialogueRootState::MyListings( MyListingsState::ViewingListings )] + .filter_map_async(find_or_create_db_user_from_callback_query) .endpoint(handle_viewing_listings_callback), ) .branch( case![DialogueRootState::MyListings( MyListingsState::ManagingListing(listing_id) )] + .filter_map_async(find_or_create_db_user_from_callback_query) .endpoint(handle_managing_listing_callback), ), ) @@ -76,7 +82,7 @@ async fn inline_query_extract_forward_listing( inline_query: InlineQuery, ) -> Option { let query = &inline_query.query; - info!("Try to extract forward listing from query: {}", query); + info!("Try to extract forward listing from query: {query}"); let listing_id_str = query.split("forward_listing:").nth(1)?; let listing_id = ListingDbId::new(listing_id_str.parse::().ok()?); let listing = ListingDAO::find_by_id(&db_pool, listing_id) @@ -90,10 +96,7 @@ async fn handle_forward_listing( inline_query: InlineQuery, listing: PersistedListing, ) -> HandlerResult { - info!( - "Handling forward listing inline query for listing {:?}", - listing - ); + info!("Handling forward listing inline query for listing {listing:?}"); let bot_username = match bot.get_me().await?.username.as_ref() { Some(username) => username.to_string(), @@ -188,68 +191,46 @@ async fn handle_my_listings_command_input( db_pool: SqlitePool, bot: Bot, dialogue: RootDialogue, + user: PersistedUser, msg: Message, ) -> HandlerResult { - let from = msg.from.unwrap(); - show_listings_for_user(db_pool, dialogue, bot, from.id, msg.chat).await?; + enter_my_listings(db_pool, bot, dialogue, user, msg.chat).await?; Ok(()) } -pub async fn show_listings_for_user( +pub async fn enter_my_listings( db_pool: SqlitePool, - dialogue: RootDialogue, bot: Bot, - user: teloxide::types::UserId, + dialogue: RootDialogue, + user: PersistedUser, target: impl Into, ) -> HandlerResult { - // If we reach here, show the listings menu - let user = match UserDAO::find_by_telegram_id(&db_pool, user).await? { - Some(user) => user, - None => { - send_message( - &bot, - target, - "You don't have an account. Try creating an auction first.", - None, - ) - .await?; - return Err(anyhow::anyhow!("User not found")); - } - }; - // Transition to ViewingListings state dialogue.update(MyListingsState::ViewingListings).await?; let listings = ListingDAO::find_by_seller(&db_pool, user.persisted.id).await?; - if listings.is_empty() { - // Create keyboard with just the back button - let keyboard = - teloxide::types::InlineKeyboardMarkup::new([[MyListingsButtons::BackToMenu.into()]]); + // Create keyboard with buttons for each listing + let mut keyboard = teloxide::types::InlineKeyboardMarkup::default(); + for listing in &listings { + keyboard = keyboard.append_row(vec![MyListingsButtons::listing_into_button(listing)]); + } + keyboard = keyboard.append_row(vec![ + MyListingsButtons::new_listing_into_button(), + MyListingsButtons::back_to_menu_into_button(), + ]); + if listings.is_empty() { send_message( &bot, target, "📋 My Listings\n\n\ - You don't have any listings yet.\n\ - Use /newlisting to create your first listing!", + You don't have any listings yet.", Some(keyboard), ) .await?; return Ok(()); } - // Create keyboard with buttons for each listing - let mut keyboard = teloxide::types::InlineKeyboardMarkup::default(); - for listing in &listings { - keyboard = keyboard.append_row(vec![InlineKeyboardButton::callback( - listing.base.title.to_string(), - listing.persisted.id.to_string(), - )]); - } - - // Add back to menu button - keyboard = keyboard.append_row(vec![MyListingsButtons::BackToMenu.into()]); - let response = format!( "📋 My Listings\n\n\ You have {}.\n\n\ @@ -266,30 +247,32 @@ async fn handle_viewing_listings_callback( bot: Bot, dialogue: RootDialogue, callback_query: CallbackQuery, + user: PersistedUser, ) -> HandlerResult { let (data, from, message_id) = extract_callback_data(&bot, callback_query).await?; let target = (from.clone(), message_id); // Check if it's the back to menu button - if let Ok(button) = MyListingsButtons::try_from(data.as_str()) { - match button { - MyListingsButtons::BackToMenu => { - // Transition back to main menu using the reusable function - enter_main_menu(bot, dialogue, target).await?; - return Ok(()); - } + let button = MyListingsButtons::try_from(data.as_str())?; + + match button { + MyListingsButtons::SelectListing(listing_id) => { + let listing = + get_listing_for_user(&db_pool, &bot, user, listing_id, target.clone()).await?; + dialogue + .update(MyListingsState::ManagingListing(listing_id)) + .await?; + show_listing_details(&bot, listing, target).await?; + } + MyListingsButtons::NewListing => { + enter_select_new_listing_type(bot, dialogue, target).await?; + } + MyListingsButtons::BackToMenu => { + // Transition back to main menu using the reusable function + enter_main_menu(bot, dialogue, target).await? } } - // Otherwise, treat it as a listing ID - let listing_id = ListingDbId::new(data.parse::()?); - let (_, listing) = - get_user_and_listing(&db_pool, &bot, from.id, listing_id, target.clone()).await?; - dialogue - .update(MyListingsState::ManagingListing(listing_id)) - .await?; - show_listing_details(&bot, listing, target).await?; - Ok(()) } @@ -339,6 +322,7 @@ async fn handle_managing_listing_callback( bot: Bot, dialogue: RootDialogue, callback_query: CallbackQuery, + user: PersistedUser, listing_id: ListingDbId, ) -> HandlerResult { let (data, from, message_id) = extract_callback_data(&bot, callback_query).await?; @@ -346,16 +330,17 @@ async fn handle_managing_listing_callback( match ManageListingButtons::try_from(data.as_str())? { ManageListingButtons::PreviewMessage => { - let (_, listing) = - get_user_and_listing(&db_pool, &bot, from.id, listing_id, target.clone()).await?; + let listing = ListingDAO::find_by_id(&db_pool, listing_id) + .await? + .ok_or(anyhow::anyhow!("Listing not found"))?; send_preview_listing_message(&bot, listing, from).await?; } ManageListingButtons::ForwardListing => { unimplemented!("Forward listing not implemented"); } ManageListingButtons::Edit => { - let (_, listing) = - get_user_and_listing(&db_pool, &bot, from.id, listing_id, target.clone()).await?; + let listing = + get_listing_for_user(&db_pool, &bot, user, listing_id, target.clone()).await?; let draft = ListingDraft::from_persisted(listing); enter_edit_listing_draft(&bot, target, draft, dialogue, None).await?; } @@ -365,7 +350,7 @@ async fn handle_managing_listing_callback( } ManageListingButtons::Back => { dialogue.update(MyListingsState::ViewingListings).await?; - show_listings_for_user(db_pool, dialogue, bot, from.id, target).await?; + enter_my_listings(db_pool, bot, dialogue, user, target).await?; } } @@ -411,7 +396,7 @@ async fn send_preview_listing_message( let mut response_lines = vec![]; response_lines.push(format!("{}", &listing.base.title)); if let Some(description) = &listing.base.description { - response_lines.push(format!("{}", description)); + response_lines.push(description.to_owned()); } send_message( bot, @@ -423,27 +408,13 @@ async fn send_preview_listing_message( Ok(()) } -async fn get_user_and_listing( +async fn get_listing_for_user( db_pool: &SqlitePool, bot: &Bot, - user_id: teloxide::types::UserId, + user: PersistedUser, listing_id: ListingDbId, target: impl Into, -) -> HandlerResult<(PersistedUser, PersistedListing)> { - let user = match UserDAO::find_by_telegram_id(db_pool, user_id).await? { - Some(user) => user, - None => { - send_message( - bot, - target, - "❌ You don't have an account. Try creating an auction first.", - None, - ) - .await?; - return Err(anyhow::anyhow!("User not found")); - } - }; - +) -> HandlerResult { let listing = match ListingDAO::find_by_id(db_pool, listing_id).await? { Some(listing) => listing, None => { @@ -463,5 +434,5 @@ async fn get_user_and_listing( return Err(anyhow::anyhow!("User does not own listing")); } - Ok((user, listing)) + Ok(listing) } diff --git a/src/commands/new_listing/callbacks.rs b/src/commands/new_listing/callbacks.rs index 59205be..b63ae2a 100644 --- a/src/commands/new_listing/callbacks.rs +++ b/src/commands/new_listing/callbacks.rs @@ -6,6 +6,7 @@ use crate::{ commands::{ new_listing::{ + enter_select_new_listing_type, field_processing::transition_to_field, keyboard::{ DurationKeyboardButtons, ListingTypeKeyboardButtons, SlotsKeyboardButtons, @@ -17,7 +18,7 @@ use crate::{ }, start::enter_main_menu, }, - db::{listing::ListingFields, ListingDuration, ListingType, UserDbId}, + db::{listing::ListingFields, user::PersistedUser, ListingDuration, ListingType}, message_utils::*, HandlerResult, RootDialogue, }; @@ -28,7 +29,7 @@ use teloxide::{types::CallbackQuery, Bot}; pub async fn handle_selecting_listing_type_callback( bot: Bot, dialogue: RootDialogue, - seller_id: UserDbId, + user: PersistedUser, callback_query: CallbackQuery, ) -> HandlerResult { let (data, from, message_id) = extract_callback_data(&bot, callback_query).await?; @@ -52,7 +53,7 @@ pub async fn handle_selecting_listing_type_callback( }; // Create draft with selected listing type - let draft = ListingDraft::new_for_seller_with_type(seller_id, listing_type); + let draft = ListingDraft::new_for_seller_with_type(user.persisted.id, listing_type); // Transition to first field (Title) transition_to_field(dialogue, ListingField::Title, draft).await?; @@ -86,8 +87,12 @@ pub async fn handle_awaiting_draft_field_callback( info!("User {from:?} selected callback: {data:?}"); let target = (from, message_id); + if let Ok(ListingTypeKeyboardButtons::Back) = data.as_str().try_into() { + return enter_select_new_listing_type(bot, dialogue, target).await; + } + if data == "cancel" { - return cancel_wizard(&bot, dialogue, target).await; + return cancel_wizard(bot, dialogue, target).await; } // Unified callback dispatch @@ -248,13 +253,12 @@ async fn handle_duration_callback( /// Cancel the wizard and exit pub async fn cancel_wizard( - bot: &Bot, + bot: Bot, dialogue: RootDialogue, target: impl Into, ) -> HandlerResult { let target = target.into(); info!("{target:?} cancelled new listing wizard"); - dialogue.exit().await?; - send_message(bot, target, "❌ Listing creation cancelled.", None).await?; + enter_select_new_listing_type(bot, dialogue, target).await?; Ok(()) } diff --git a/src/commands/new_listing/handler_factory.rs b/src/commands/new_listing/handler_factory.rs index 4c56e82..c512342 100644 --- a/src/commands/new_listing/handler_factory.rs +++ b/src/commands/new_listing/handler_factory.rs @@ -1,5 +1,8 @@ use super::{callbacks::*, handlers::*, types::*}; -use crate::{case, Command, DialogueRootState, Handler}; +use crate::{ + case, handler_utils::find_or_create_db_user_from_callback_query, Command, DialogueRootState, + Handler, +}; use teloxide::{dptree, prelude::*, types::Update}; // Create the dialogue handler tree for new listing wizard @@ -30,8 +33,9 @@ pub fn new_listing_handler() -> Handler { Update::filter_callback_query() .branch( case![DialogueRootState::NewListing( - NewListingState::SelectingListingType { seller_id } + NewListingState::SelectingListingType )] + .filter_map_async(find_or_create_db_user_from_callback_query) .endpoint(handle_selecting_listing_type_callback), ) .branch( diff --git a/src/commands/new_listing/handlers.rs b/src/commands/new_listing/handlers.rs index 55aa25e..4a20bc0 100644 --- a/src/commands/new_listing/handlers.rs +++ b/src/commands/new_listing/handlers.rs @@ -20,41 +20,33 @@ use crate::{ }, db::{ listing::{ListingFields, NewListing, PersistedListing}, - ListingDAO, UserDAO, + ListingDAO, }, message_utils::*, DialogueRootState, HandlerResult, RootDialogue, }; -use log::{error, info}; +use log::info; use sqlx::SqlitePool; use teloxide::{prelude::*, types::*, Bot}; /// Handle the /newlisting command - starts the dialogue pub(super) async fn handle_new_listing_command( - db_pool: SqlitePool, bot: Bot, dialogue: RootDialogue, msg: Message, ) -> HandlerResult { - let user = msg.from.ok_or_else(|| anyhow::anyhow!("User not found"))?; - enter_handle_new_listing(db_pool, bot, dialogue, user, msg.chat).await?; + enter_select_new_listing_type(bot, dialogue, msg.chat).await?; Ok(()) } -pub async fn enter_handle_new_listing( - db_pool: SqlitePool, +pub async fn enter_select_new_listing_type( bot: Bot, dialogue: RootDialogue, - user: User, target: impl Into, ) -> HandlerResult { - let user = UserDAO::find_or_create_by_telegram_user(&db_pool, user).await?; - // Initialize the dialogue to listing type selection state dialogue - .update(NewListingState::SelectingListingType { - seller_id: user.persisted.id, - }) + .update(NewListingState::SelectingListingType) .await?; send_message( @@ -84,7 +76,7 @@ pub async fn handle_awaiting_draft_field_input( ); if is_cancel(text) { - return cancel_wizard(&bot, dialogue, chat).await; + return cancel_wizard(bot, dialogue, chat).await; } // Process the field update @@ -145,14 +137,6 @@ pub async fn handle_viewing_draft_callback( callback_query: CallbackQuery, ) -> HandlerResult { let (data, from, message_id) = extract_callback_data(&bot, callback_query).await?; - - // Ensure the user exists before saving the listing - UserDAO::find_or_create_by_telegram_user(&db_pool, from.clone()) - .await - .inspect_err(|e| { - error!("Error finding or creating user: {e}"); - })?; - let target = (from.clone(), message_id); match ConfirmationKeyboardButtons::try_from(data.as_str())? { diff --git a/src/commands/new_listing/messages.rs b/src/commands/new_listing/messages.rs index f5246be..6d68368 100644 --- a/src/commands/new_listing/messages.rs +++ b/src/commands/new_listing/messages.rs @@ -54,7 +54,10 @@ pub fn get_edit_success_message(field: ListingField) -> &'static str { /// Get the appropriate keyboard for a field pub fn get_keyboard_for_field(field: ListingField) -> Option { match field { - ListingField::Title => Some(create_cancel_keyboard()), + ListingField::Title => Some(InlineKeyboardMarkup::new([[ + // Back to listing type selection + ListingTypeKeyboardButtons::Back.to_button(), + ]])), ListingField::Description => Some(create_skip_cancel_keyboard()), ListingField::Price => None, ListingField::Slots => Some(SlotsKeyboardButtons::to_keyboard()), @@ -63,11 +66,6 @@ pub fn get_keyboard_for_field(field: ListingField) -> Option InlineKeyboardMarkup { - create_single_button_keyboard("Cancel", "cancel") -} - fn create_skip_cancel_keyboard() -> InlineKeyboardMarkup { create_multi_row_keyboard(&[&[("Skip", "skip"), ("Cancel", "cancel")]]) } diff --git a/src/commands/new_listing/mod.rs b/src/commands/new_listing/mod.rs index 95df398..fd1e4b5 100644 --- a/src/commands/new_listing/mod.rs +++ b/src/commands/new_listing/mod.rs @@ -20,12 +20,11 @@ mod keyboard; pub mod messages; #[cfg(test)] mod tests; - mod types; mod ui; mod validations; // Re-export the main handler for external use pub use handler_factory::new_listing_handler; -pub use handlers::{enter_edit_listing_draft, enter_handle_new_listing}; +pub use handlers::{enter_edit_listing_draft, enter_select_new_listing_type}; pub use types::*; diff --git a/src/commands/new_listing/types.rs b/src/commands/new_listing/types.rs index 9cc669a..fdec1e0 100644 --- a/src/commands/new_listing/types.rs +++ b/src/commands/new_listing/types.rs @@ -89,9 +89,7 @@ pub enum ListingField { // Dialogue state for the new listing wizard #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub enum NewListingState { - SelectingListingType { - seller_id: UserDbId, - }, + SelectingListingType, AwaitingDraftField { field: ListingField, draft: ListingDraft, diff --git a/src/commands/start.rs b/src/commands/start.rs index c83e3dd..cc2de35 100644 --- a/src/commands/start.rs +++ b/src/commands/start.rs @@ -8,7 +8,8 @@ use teloxide::{ use sqlx::SqlitePool; use crate::{ - commands::{my_listings::show_listings_for_user, new_listing::enter_handle_new_listing}, + commands::my_listings::enter_my_listings, + db::user::PersistedUser, keyboard_buttons, message_utils::{extract_callback_data, send_message, MessageTarget}, Command, DialogueRootState, HandlerResult, RootDialogue, @@ -16,9 +17,6 @@ use crate::{ keyboard_buttons! { pub enum MainMenuButtons { - [ - NewListing("🛍️ New Listing", "menu_new_listing"), - ], [ MyListings("📋 My Listings", "menu_my_listings"), MyBids("💰 My Bids", "menu_my_bids"), @@ -69,6 +67,7 @@ pub async fn handle_main_menu_callback( db_pool: SqlitePool, bot: Bot, dialogue: RootDialogue, + user: PersistedUser, callback_query: CallbackQuery, ) -> HandlerResult { let (data, from, message_id) = extract_callback_data(&bot, callback_query).await?; @@ -82,12 +81,9 @@ pub async fn handle_main_menu_callback( let button = MainMenuButtons::try_from(data.as_str())?; match button { - MainMenuButtons::NewListing => { - enter_handle_new_listing(db_pool, bot, dialogue, from.clone(), target).await?; - } MainMenuButtons::MyListings => { // Call show_listings_for_user directly - show_listings_for_user(db_pool, dialogue, bot, from.id, target).await?; + enter_my_listings(db_pool, bot, dialogue, user, target).await?; } MainMenuButtons::MyBids => { send_message( diff --git a/src/db/models/user.rs b/src/db/models/user.rs index aa87e1b..ebeff51 100644 --- a/src/db/models/user.rs +++ b/src/db/models/user.rs @@ -8,7 +8,7 @@ pub type PersistedUser = User; pub type NewUser = User<()>; /// Core user information -#[derive(Debug, Clone, FromRow)] +#[derive(Clone, FromRow)] #[allow(unused)] pub struct User { pub persisted: P, @@ -19,6 +19,22 @@ pub struct User { pub is_banned: bool, } +impl Debug for User { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let name = if let Some(last_name) = self.last_name.as_deref() { + format!("{} {}", self.first_name, last_name) + } else { + self.first_name.clone() + }; + let username = self.username.as_deref().unwrap_or(""); + write!( + f, + "User(id: {} / {}, '{}' @{})", + self.persisted.id, self.telegram_id, name, username + ) + } +} + #[derive(Debug, Clone)] #[allow(unused)] pub struct PersistedUserFields { diff --git a/src/handler_utils.rs b/src/handler_utils.rs new file mode 100644 index 0000000..2945dad --- /dev/null +++ b/src/handler_utils.rs @@ -0,0 +1,36 @@ +use sqlx::SqlitePool; +use teloxide::types::{CallbackQuery, Message}; + +use crate::db::{user::PersistedUser, UserDAO}; + +pub async fn find_or_create_db_user_from_message( + db_pool: SqlitePool, + message: Message, +) -> Option { + let user = message.from?; + find_or_create_db_user(db_pool, user).await +} + +pub async fn find_or_create_db_user_from_callback_query( + db_pool: SqlitePool, + callback_query: CallbackQuery, +) -> Option { + let user = callback_query.from; + find_or_create_db_user(db_pool, user).await +} + +pub async fn find_or_create_db_user( + db_pool: SqlitePool, + user: teloxide::types::User, +) -> Option { + match UserDAO::find_or_create_by_telegram_user(&db_pool, user).await { + Ok(user) => { + log::debug!("loaded user from db: {user:?}"); + Some(user) + } + Err(e) => { + log::error!("Error finding or creating user: {e}"); + None + } + } +} diff --git a/src/keyboard_utils.rs b/src/keyboard_utils.rs index 18dc716..c21a196 100644 --- a/src/keyboard_utils.rs +++ b/src/keyboard_utils.rs @@ -55,6 +55,13 @@ macro_rules! keyboard_buttons { $($($name::$variant => $text),*),* } } + + #[allow(unused)] + pub fn callback_data(self) -> &'static str { + match self { + $($($name::$variant => $callback_data),*),* + } + } } impl From<$name> for teloxide::types::InlineKeyboardButton { fn from(value: $name) -> Self { diff --git a/src/main.rs b/src/main.rs index 673793b..18dbfb5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,17 +2,21 @@ mod commands; mod config; mod db; mod dptree_utils; +mod handler_utils; mod keyboard_utils; mod message_utils; mod sqlite_storage; #[cfg(test)] mod test_utils; -use crate::commands::{ - my_listings::{my_listings_handler, my_listings_inline_handler, MyListingsState}, - new_listing::{new_listing_handler, NewListingState}, -}; use crate::sqlite_storage::SqliteStorage; +use crate::{ + commands::{ + my_listings::{my_listings_handler, my_listings_inline_handler, MyListingsState}, + new_listing::{new_listing_handler, NewListingState}, + }, + handler_utils::find_or_create_db_user_from_callback_query, +}; use anyhow::Result; use commands::*; use config::Config; @@ -98,6 +102,7 @@ async fn main() -> Result<()> { .branch( Update::filter_callback_query().branch( dptree::case![DialogueRootState::MainMenu] + .filter_map_async(find_or_create_db_user_from_callback_query) .endpoint(handle_main_menu_callback), ), )