Refactor bot commands and database models

- Update my_listings command structure and keyboard handling
- Enhance new_listing workflow with improved callbacks and handlers
- Refactor database user model and keyboard utilities
- Add new handler utilities module
- Update main bot configuration and start command
This commit is contained in:
Dylan Knutson
2025-08-30 11:04:40 -07:00
parent 65a50b05e2
commit a39dd01452
15 changed files with 220 additions and 146 deletions

1
Cargo.lock generated
View File

@@ -1618,6 +1618,7 @@ dependencies = [
"lazy_static",
"log",
"num",
"regex",
"rstest",
"rust_decimal",
"serde",

View File

@@ -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"

View File

@@ -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<ListingDbId> {
let re = Regex::new(r"my_listings:(\d+)").ok()?;
let caps = re.captures(value)?;
let listing_id = caps.get(1)?.as_str().parse::<i64>().ok()?;
Some(ListingDbId::new(listing_id))
}
}
impl TryFrom<&str> for MyListingsButtons {
type Error = anyhow::Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
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}"),
}
}
}

View File

@@ -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<MyListingsState> 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::<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<PersistedListing> {
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::<i64>().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<MessageTarget>,
) -> 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,
"📋 <b>My Listings</b>\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!(
"📋 <b>My Listings</b>\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::<i64>()?);
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!("<b>{}</b>", &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<MessageTarget>,
) -> 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<PersistedListing> {
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)
}

View File

@@ -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<MessageTarget>,
) -> 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(())
}

View File

@@ -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(

View File

@@ -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<MessageTarget>,
) -> 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())? {

View File

@@ -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<InlineKeyboardMarkup> {
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<InlineKeyboardMarku
}
}
// Keyboard creation helpers
fn create_cancel_keyboard() -> InlineKeyboardMarkup {
create_single_button_keyboard("Cancel", "cancel")
}
fn create_skip_cancel_keyboard() -> InlineKeyboardMarkup {
create_multi_row_keyboard(&[&[("Skip", "skip"), ("Cancel", "cancel")]])
}

View File

@@ -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::*;

View File

@@ -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,

View File

@@ -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(

View File

@@ -8,7 +8,7 @@ pub type PersistedUser = User<PersistedUserFields>;
pub type NewUser = User<()>;
/// Core user information
#[derive(Debug, Clone, FromRow)]
#[derive(Clone, FromRow)]
#[allow(unused)]
pub struct User<P: Debug + Clone> {
pub persisted: P,
@@ -19,6 +19,22 @@ pub struct User<P: Debug + Clone> {
pub is_banned: bool,
}
impl Debug for User<PersistedUserFields> {
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 {

36
src/handler_utils.rs Normal file
View File

@@ -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<PersistedUser> {
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<PersistedUser> {
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<PersistedUser> {
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
}
}
}

View File

@@ -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 {

View File

@@ -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),
),
)