move target into state
This commit is contained in:
@@ -12,7 +12,6 @@ use crate::{
|
|||||||
dptree_utils::MapTwo,
|
dptree_utils::MapTwo,
|
||||||
handle_error::with_error_handler,
|
handle_error::with_error_handler,
|
||||||
handler_utils::find_listing_by_id,
|
handler_utils::find_listing_by_id,
|
||||||
message_utils::MessageTarget,
|
|
||||||
start_command_data::StartCommandData,
|
start_command_data::StartCommandData,
|
||||||
App, BotError, BotHandler, BotResult, DialogueRootState, RootDialogue,
|
App, BotError, BotHandler, BotResult, DialogueRootState, RootDialogue,
|
||||||
};
|
};
|
||||||
@@ -75,7 +74,6 @@ pub fn bidding_handler() -> BotHandler {
|
|||||||
async fn handle_place_bid_on_listing(
|
async fn handle_place_bid_on_listing(
|
||||||
app: App,
|
app: App,
|
||||||
user_dao: UserDAO,
|
user_dao: UserDAO,
|
||||||
target: MessageTarget,
|
|
||||||
user: PersistedUser,
|
user: PersistedUser,
|
||||||
listing: PersistedListing,
|
listing: PersistedListing,
|
||||||
dialogue: RootDialogue,
|
dialogue: RootDialogue,
|
||||||
@@ -116,7 +114,7 @@ async fn handle_place_bid_on_listing(
|
|||||||
.append_row([InlineKeyboardButton::callback("Bid $1", "cancel")]);
|
.append_row([InlineKeyboardButton::callback("Bid $1", "cancel")]);
|
||||||
|
|
||||||
app.bot
|
app.bot
|
||||||
.send_html_message(target, response_lines.join("\n"), Some(keyboard))
|
.send_html_message(response_lines.join("\n"), Some(keyboard))
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -125,7 +123,6 @@ async fn handle_place_bid_on_listing(
|
|||||||
async fn handle_awaiting_bid_amount_input(
|
async fn handle_awaiting_bid_amount_input(
|
||||||
app: App,
|
app: App,
|
||||||
listing: PersistedListing,
|
listing: PersistedListing,
|
||||||
target: MessageTarget,
|
|
||||||
dialogue: RootDialogue,
|
dialogue: RootDialogue,
|
||||||
msg: Message,
|
msg: Message,
|
||||||
) -> BotResult {
|
) -> BotResult {
|
||||||
@@ -146,7 +143,6 @@ async fn handle_awaiting_bid_amount_input(
|
|||||||
let bid_amount_str = format!("{}{}", listing.base.currency_type.symbol(), bid_amount);
|
let bid_amount_str = format!("{}{}", listing.base.currency_type.symbol(), bid_amount);
|
||||||
app.bot
|
app.bot
|
||||||
.send_html_message(
|
.send_html_message(
|
||||||
target,
|
|
||||||
format!("Confirm bid amount: {bid_amount_str} - this cannot be undone!"),
|
format!("Confirm bid amount: {bid_amount_str} - this cannot be undone!"),
|
||||||
Some(InlineKeyboardMarkup::default().append_row([
|
Some(InlineKeyboardMarkup::default().append_row([
|
||||||
InlineKeyboardButton::callback(
|
InlineKeyboardButton::callback(
|
||||||
@@ -174,7 +170,6 @@ async fn handle_awaiting_confirm_bid_amount_callback(
|
|||||||
listing: PersistedListing,
|
listing: PersistedListing,
|
||||||
user: PersistedUser,
|
user: PersistedUser,
|
||||||
bid_amount: MoneyAmount,
|
bid_amount: MoneyAmount,
|
||||||
target: MessageTarget,
|
|
||||||
dialogue: RootDialogue,
|
dialogue: RootDialogue,
|
||||||
callback_query: CallbackQuery,
|
callback_query: CallbackQuery,
|
||||||
) -> BotResult {
|
) -> BotResult {
|
||||||
@@ -188,7 +183,7 @@ async fn handle_awaiting_confirm_bid_amount_callback(
|
|||||||
"cancel_bid" => {
|
"cancel_bid" => {
|
||||||
dialogue.exit().await.context("failed to exit dialogue")?;
|
dialogue.exit().await.context("failed to exit dialogue")?;
|
||||||
app.bot
|
app.bot
|
||||||
.send_html_message(target, "Bid cancelled".to_string(), None)
|
.send_html_message("Bid cancelled".to_string(), None)
|
||||||
.await?;
|
.await?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -206,8 +201,8 @@ async fn handle_awaiting_confirm_bid_amount_callback(
|
|||||||
|
|
||||||
let bid_amount_str = format!("{}{}", listing.base.currency_type.symbol(), bid_amount);
|
let bid_amount_str = format!("{}{}", listing.base.currency_type.symbol(), bid_amount);
|
||||||
app.bot
|
app.bot
|
||||||
|
.with_target(callback_query.from.into())
|
||||||
.send_html_message(
|
.send_html_message(
|
||||||
target.only_chat_id(),
|
|
||||||
format!("Bid placed for {bid_amount_str} on {}", listing.base.title),
|
format!("Bid placed for {bid_amount_str} on {}", listing.base.title),
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::{message_utils::MessageTarget, App, BotResult, Command};
|
use crate::{App, BotResult, Command};
|
||||||
use teloxide::utils::command::BotCommands;
|
use teloxide::utils::command::BotCommands;
|
||||||
|
|
||||||
pub async fn handle_help(app: App, target: MessageTarget) -> BotResult {
|
pub async fn handle_help(app: App) -> BotResult {
|
||||||
let help_message = format!(
|
let help_message = format!(
|
||||||
"📋 Available Commands:\n\n{}\n\n\
|
"📋 Available Commands:\n\n{}\n\n\
|
||||||
📧 Support: Contact @admin for help\n\
|
📧 Support: Contact @admin for help\n\
|
||||||
@@ -9,8 +9,6 @@ pub async fn handle_help(app: App, target: MessageTarget) -> BotResult {
|
|||||||
Command::descriptions()
|
Command::descriptions()
|
||||||
);
|
);
|
||||||
|
|
||||||
app.bot
|
app.bot.send_html_message(help_message, None).await?;
|
||||||
.send_html_message(target, help_message, None)
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use crate::{message_utils::MessageTarget, App, BotResult};
|
use crate::{App, BotResult};
|
||||||
use log::info;
|
use log::info;
|
||||||
use teloxide::types::Message;
|
use teloxide::types::Message;
|
||||||
|
|
||||||
pub async fn handle_my_bids(app: App, msg: Message, target: MessageTarget) -> BotResult {
|
pub async fn handle_my_bids(app: App, msg: Message) -> BotResult {
|
||||||
let response = "🎯 My Bids (Coming Soon)\n\n\
|
let response = "🎯 My Bids (Coming Soon)\n\n\
|
||||||
Here you'll be able to view:\n\
|
Here you'll be able to view:\n\
|
||||||
• Your active bids\n\
|
• Your active bids\n\
|
||||||
@@ -18,6 +18,6 @@ pub async fn handle_my_bids(app: App, msg: Message, target: MessageTarget) -> Bo
|
|||||||
msg.chat.id
|
msg.chat.id
|
||||||
);
|
);
|
||||||
|
|
||||||
app.bot.send_html_message(target, response, None).await?;
|
app.bot.send_html_message(response, None).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ use crate::{
|
|||||||
},
|
},
|
||||||
handle_error::with_error_handler,
|
handle_error::with_error_handler,
|
||||||
handler_utils::{find_listing_by_id, find_or_create_db_user_from_update},
|
handler_utils::{find_listing_by_id, find_or_create_db_user_from_update},
|
||||||
message_utils::{extract_callback_data, pluralize_with_count, MessageTarget},
|
message_utils::{extract_callback_data, pluralize_with_count},
|
||||||
start_command_data::StartCommandData,
|
start_command_data::StartCommandData,
|
||||||
App, BotError, BotResult, Command, DialogueRootState, RootDialogue,
|
App, BotError, BotResult, Command, DialogueRootState, RootDialogue,
|
||||||
};
|
};
|
||||||
@@ -87,12 +87,8 @@ pub fn my_listings_handler() -> Handler<'static, BotResult, DpHandlerDescription
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_view_listing_details(
|
async fn handle_view_listing_details(app: App, listing: PersistedListing) -> BotResult {
|
||||||
app: App,
|
send_listing_details_message(app, listing, None).await?;
|
||||||
listing: PersistedListing,
|
|
||||||
target: MessageTarget,
|
|
||||||
) -> BotResult {
|
|
||||||
send_listing_details_message(app, target, listing, None).await?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,9 +226,8 @@ async fn handle_my_listings_command_input(
|
|||||||
app: App,
|
app: App,
|
||||||
dialogue: RootDialogue,
|
dialogue: RootDialogue,
|
||||||
user: PersistedUser,
|
user: PersistedUser,
|
||||||
target: MessageTarget,
|
|
||||||
) -> BotResult {
|
) -> BotResult {
|
||||||
enter_my_listings(app, dialogue, user, target, None).await?;
|
enter_my_listings(app, dialogue, user, None).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,7 +235,6 @@ pub async fn enter_my_listings(
|
|||||||
app: App,
|
app: App,
|
||||||
dialogue: RootDialogue,
|
dialogue: RootDialogue,
|
||||||
user: PersistedUser,
|
user: PersistedUser,
|
||||||
target: MessageTarget,
|
|
||||||
flash: Option<String>,
|
flash: Option<String>,
|
||||||
) -> BotResult {
|
) -> BotResult {
|
||||||
// Transition to ViewingListings state
|
// Transition to ViewingListings state
|
||||||
@@ -263,7 +257,6 @@ pub async fn enter_my_listings(
|
|||||||
if listings.is_empty() {
|
if listings.is_empty() {
|
||||||
app.bot
|
app.bot
|
||||||
.send_html_message(
|
.send_html_message(
|
||||||
target,
|
|
||||||
"📋 <b>My Listings</b>\n\n\
|
"📋 <b>My Listings</b>\n\n\
|
||||||
You don't have any listings yet."
|
You don't have any listings yet."
|
||||||
.to_string(),
|
.to_string(),
|
||||||
@@ -284,9 +277,7 @@ pub async fn enter_my_listings(
|
|||||||
response = format!("{flash}\n\n{response}");
|
response = format!("{flash}\n\n{response}");
|
||||||
}
|
}
|
||||||
|
|
||||||
app.bot
|
app.bot.send_html_message(response, Some(keyboard)).await?;
|
||||||
.send_html_message(target, response, Some(keyboard))
|
|
||||||
.await?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,12 +286,11 @@ async fn handle_viewing_listings_callback(
|
|||||||
dialogue: RootDialogue,
|
dialogue: RootDialogue,
|
||||||
callback_query: CallbackQuery,
|
callback_query: CallbackQuery,
|
||||||
user: PersistedUser,
|
user: PersistedUser,
|
||||||
target: MessageTarget,
|
|
||||||
) -> BotResult {
|
) -> BotResult {
|
||||||
let data = extract_callback_data(app.bot.deref(), callback_query).await?;
|
let data = extract_callback_data(app.bot.deref(), callback_query).await?;
|
||||||
|
|
||||||
if let Ok(NavKeyboardButtons::Back) = NavKeyboardButtons::try_from(data.as_str()) {
|
if let Ok(NavKeyboardButtons::Back) = NavKeyboardButtons::try_from(data.as_str()) {
|
||||||
return enter_main_menu(app, dialogue, target).await;
|
return enter_main_menu(app, dialogue).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if it's the back to menu button
|
// Check if it's the back to menu button
|
||||||
@@ -308,10 +298,10 @@ async fn handle_viewing_listings_callback(
|
|||||||
match button {
|
match button {
|
||||||
MyListingsButtons::SelectListing(listing_id) => {
|
MyListingsButtons::SelectListing(listing_id) => {
|
||||||
let listing = get_listing_for_user(&app.daos, user, listing_id).await?;
|
let listing = get_listing_for_user(&app.daos, user, listing_id).await?;
|
||||||
enter_show_listing_details(app, dialogue, listing, target).await?;
|
enter_show_listing_details(app, dialogue, listing).await?;
|
||||||
}
|
}
|
||||||
MyListingsButtons::NewListing => {
|
MyListingsButtons::NewListing => {
|
||||||
enter_select_new_listing_type(app, dialogue, target).await?;
|
enter_select_new_listing_type(app, dialogue).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -322,7 +312,6 @@ async fn enter_show_listing_details(
|
|||||||
app: App,
|
app: App,
|
||||||
dialogue: RootDialogue,
|
dialogue: RootDialogue,
|
||||||
listing: PersistedListing,
|
listing: PersistedListing,
|
||||||
target: MessageTarget,
|
|
||||||
) -> BotResult {
|
) -> BotResult {
|
||||||
let listing_id = listing.persisted.id;
|
let listing_id = listing.persisted.id;
|
||||||
dialogue
|
dialogue
|
||||||
@@ -342,13 +331,12 @@ async fn enter_show_listing_details(
|
|||||||
ManageListingButtons::Delete.to_button(),
|
ManageListingButtons::Delete.to_button(),
|
||||||
])
|
])
|
||||||
.append_row([ManageListingButtons::Back.to_button()]);
|
.append_row([ManageListingButtons::Back.to_button()]);
|
||||||
send_listing_details_message(app, target, listing, Some(keyboard)).await?;
|
send_listing_details_message(app, listing, Some(keyboard)).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn send_listing_details_message(
|
async fn send_listing_details_message(
|
||||||
app: App,
|
app: App,
|
||||||
target: MessageTarget,
|
|
||||||
listing: PersistedListing,
|
listing: PersistedListing,
|
||||||
keyboard: Option<InlineKeyboardMarkup>,
|
keyboard: Option<InlineKeyboardMarkup>,
|
||||||
) -> BotResult {
|
) -> BotResult {
|
||||||
@@ -365,7 +353,7 @@ async fn send_listing_details_message(
|
|||||||
response_lines.push(format!("<b>{}:</b> {}", step.field_name, field_value));
|
response_lines.push(format!("<b>{}:</b> {}", step.field_name, field_value));
|
||||||
}
|
}
|
||||||
app.bot
|
app.bot
|
||||||
.send_html_message(target, response_lines.join("\n"), keyboard)
|
.send_html_message(response_lines.join("\n"), keyboard)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -376,7 +364,6 @@ async fn handle_managing_listing_callback(
|
|||||||
callback_query: CallbackQuery,
|
callback_query: CallbackQuery,
|
||||||
user: PersistedUser,
|
user: PersistedUser,
|
||||||
listing_id: ListingDbId,
|
listing_id: ListingDbId,
|
||||||
target: MessageTarget,
|
|
||||||
) -> BotResult {
|
) -> BotResult {
|
||||||
let from = callback_query.from.clone();
|
let from = callback_query.from.clone();
|
||||||
let data = extract_callback_data(&app.bot, callback_query).await?;
|
let data = extract_callback_data(&app.bot, callback_query).await?;
|
||||||
@@ -397,21 +384,14 @@ async fn handle_managing_listing_callback(
|
|||||||
ManageListingButtons::Edit => {
|
ManageListingButtons::Edit => {
|
||||||
let listing = get_listing_for_user(&app.daos, user, listing_id).await?;
|
let listing = get_listing_for_user(&app.daos, user, listing_id).await?;
|
||||||
let draft = ListingDraft::from_persisted(listing);
|
let draft = ListingDraft::from_persisted(listing);
|
||||||
enter_edit_listing_draft(app, target, draft, dialogue, None).await?;
|
enter_edit_listing_draft(app, draft, dialogue, None).await?;
|
||||||
}
|
}
|
||||||
ManageListingButtons::Delete => {
|
ManageListingButtons::Delete => {
|
||||||
app.daos.listing.delete_listing(listing_id).await?;
|
app.daos.listing.delete_listing(listing_id).await?;
|
||||||
enter_my_listings(
|
enter_my_listings(app, dialogue, user, Some("Listing deleted.".to_string())).await?;
|
||||||
app,
|
|
||||||
dialogue,
|
|
||||||
user,
|
|
||||||
target,
|
|
||||||
Some("Listing deleted.".to_string()),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
ManageListingButtons::Back => {
|
ManageListingButtons::Back => {
|
||||||
enter_my_listings(app, dialogue, user, target, None).await?;
|
enter_my_listings(app, dialogue, user, None).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -459,9 +439,10 @@ async fn send_preview_listing_message(
|
|||||||
if let Some(description) = &listing.base.description {
|
if let Some(description) = &listing.base.description {
|
||||||
response_lines.push(description.to_owned());
|
response_lines.push(description.to_owned());
|
||||||
}
|
}
|
||||||
|
|
||||||
app.bot
|
app.bot
|
||||||
|
.with_target(from.into())
|
||||||
.send_html_message(
|
.send_html_message(
|
||||||
from.into(),
|
|
||||||
response_lines.join("\n\n"),
|
response_lines.join("\n\n"),
|
||||||
Some(keyboard_for_listing(&listing)),
|
Some(keyboard_for_listing(&listing)),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -28,13 +28,12 @@ pub async fn handle_selecting_listing_type_callback(
|
|||||||
dialogue: RootDialogue,
|
dialogue: RootDialogue,
|
||||||
user: PersistedUser,
|
user: PersistedUser,
|
||||||
callback_query: CallbackQuery,
|
callback_query: CallbackQuery,
|
||||||
target: MessageTarget,
|
|
||||||
) -> BotResult {
|
) -> BotResult {
|
||||||
let data = extract_callback_data(&app.bot, callback_query).await?;
|
let data = extract_callback_data(&app.bot, callback_query).await?;
|
||||||
info!("User {target:?} selected listing type: {data:?}");
|
info!("User selected listing type: {data:?}");
|
||||||
|
|
||||||
if let Ok(NavKeyboardButtons::Back) = NavKeyboardButtons::try_from(data.as_str()) {
|
if let Ok(NavKeyboardButtons::Back) = NavKeyboardButtons::try_from(data.as_str()) {
|
||||||
return enter_my_listings(app, dialogue, user, target, None).await;
|
return enter_my_listings(app, dialogue, user, None).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the listing type from callback data
|
// Parse the listing type from callback data
|
||||||
@@ -62,11 +61,7 @@ pub async fn handle_selecting_listing_type_callback(
|
|||||||
);
|
);
|
||||||
|
|
||||||
app.bot
|
app.bot
|
||||||
.send_html_message(
|
.send_html_message(response, get_keyboard_for_field(ListingField::Title))
|
||||||
target,
|
|
||||||
response,
|
|
||||||
get_keyboard_for_field(ListingField::Title),
|
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -79,21 +74,20 @@ pub async fn handle_awaiting_draft_field_callback(
|
|||||||
field: ListingField,
|
field: ListingField,
|
||||||
draft: ListingDraft,
|
draft: ListingDraft,
|
||||||
callback_query: CallbackQuery,
|
callback_query: CallbackQuery,
|
||||||
target: MessageTarget,
|
|
||||||
) -> BotResult {
|
) -> BotResult {
|
||||||
let data = extract_callback_data(&app.bot, callback_query).await?;
|
let data = extract_callback_data(&app.bot, callback_query).await?;
|
||||||
info!("User {target:?} selected callback: {data:?}");
|
info!("User selected callback: {data:?}");
|
||||||
|
|
||||||
if let Ok(button) = NavKeyboardButtons::try_from(data.as_str()) {
|
if let Ok(button) = NavKeyboardButtons::try_from(data.as_str()) {
|
||||||
match button {
|
match button {
|
||||||
NavKeyboardButtons::Back => {
|
NavKeyboardButtons::Back => {
|
||||||
return enter_select_new_listing_type(app, dialogue, target).await;
|
return enter_select_new_listing_type(app, dialogue).await;
|
||||||
}
|
}
|
||||||
NavKeyboardButtons::Skip => {
|
NavKeyboardButtons::Skip => {
|
||||||
return handle_skip_field(app, dialogue, field, draft, target).await;
|
return handle_skip_field(app, dialogue, field, draft).await;
|
||||||
}
|
}
|
||||||
NavKeyboardButtons::Cancel => {
|
NavKeyboardButtons::Cancel => {
|
||||||
return cancel_wizard(app, dialogue, target).await;
|
return cancel_wizard(app, dialogue).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,23 +96,23 @@ pub async fn handle_awaiting_draft_field_callback(
|
|||||||
match field {
|
match field {
|
||||||
ListingField::Slots => {
|
ListingField::Slots => {
|
||||||
let button = SlotsKeyboardButtons::try_from(data.as_str())?;
|
let button = SlotsKeyboardButtons::try_from(data.as_str())?;
|
||||||
handle_slots_callback(app, dialogue, draft, button, target).await
|
handle_slots_callback(app, dialogue, draft, button).await
|
||||||
}
|
}
|
||||||
ListingField::StartTime => {
|
ListingField::StartTime => {
|
||||||
let button = StartTimeKeyboardButtons::try_from(data.as_str())?;
|
let button = StartTimeKeyboardButtons::try_from(data.as_str())?;
|
||||||
handle_start_time_callback(app, dialogue, draft, button, target).await
|
handle_start_time_callback(app, dialogue, draft, button).await
|
||||||
}
|
}
|
||||||
ListingField::EndTime => {
|
ListingField::EndTime => {
|
||||||
let button = DurationKeyboardButtons::try_from(data.as_str())?;
|
let button = DurationKeyboardButtons::try_from(data.as_str())?;
|
||||||
handle_duration_callback(app, dialogue, draft, button, target).await
|
handle_duration_callback(app, dialogue, draft, button).await
|
||||||
}
|
}
|
||||||
ListingField::MinBidIncrement => {
|
ListingField::MinBidIncrement => {
|
||||||
let button = EditMinimumBidIncrementKeyboardButtons::try_from(data.as_str())?;
|
let button = EditMinimumBidIncrementKeyboardButtons::try_from(data.as_str())?;
|
||||||
handle_starting_bid_amount_callback(app, dialogue, draft, button, target).await
|
handle_starting_bid_amount_callback(app, dialogue, draft, button).await
|
||||||
}
|
}
|
||||||
ListingField::CurrencyType => {
|
ListingField::CurrencyType => {
|
||||||
let button = CurrencyTypeKeyboardButtons::try_from(data.as_str())?;
|
let button = CurrencyTypeKeyboardButtons::try_from(data.as_str())?;
|
||||||
handle_currency_type_callback(app, dialogue, draft, button, target).await
|
handle_currency_type_callback(app, dialogue, draft, button).await
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
error!("Unknown callback data for field {field:?}: {data}");
|
error!("Unknown callback data for field {field:?}: {data}");
|
||||||
@@ -132,7 +126,6 @@ async fn handle_skip_field(
|
|||||||
dialogue: RootDialogue,
|
dialogue: RootDialogue,
|
||||||
current_field: ListingField,
|
current_field: ListingField,
|
||||||
draft: ListingDraft,
|
draft: ListingDraft,
|
||||||
target: MessageTarget,
|
|
||||||
) -> BotResult {
|
) -> BotResult {
|
||||||
let field_name = get_field_name(current_field, draft.listing_type());
|
let field_name = get_field_name(current_field, draft.listing_type());
|
||||||
let next_field = get_next_field(current_field, draft.listing_type());
|
let next_field = get_next_field(current_field, draft.listing_type());
|
||||||
@@ -145,10 +138,10 @@ async fn handle_skip_field(
|
|||||||
);
|
);
|
||||||
transition_to_field(dialogue, next_field, draft).await?;
|
transition_to_field(dialogue, next_field, draft).await?;
|
||||||
app.bot
|
app.bot
|
||||||
.send_html_message(target, response, get_keyboard_for_field(next_field))
|
.send_html_message(response, get_keyboard_for_field(next_field))
|
||||||
.await?;
|
.await?;
|
||||||
} else {
|
} else {
|
||||||
enter_confirm_save_listing(app, dialogue, target, draft, Some(flash)).await?;
|
enter_confirm_save_listing(app, dialogue, draft, Some(flash)).await?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -159,7 +152,6 @@ async fn handle_slots_callback(
|
|||||||
dialogue: RootDialogue,
|
dialogue: RootDialogue,
|
||||||
mut draft: ListingDraft,
|
mut draft: ListingDraft,
|
||||||
button: SlotsKeyboardButtons,
|
button: SlotsKeyboardButtons,
|
||||||
target: MessageTarget,
|
|
||||||
) -> BotResult {
|
) -> BotResult {
|
||||||
let num_slots = match button {
|
let num_slots = match button {
|
||||||
SlotsKeyboardButtons::OneSlot => 1,
|
SlotsKeyboardButtons::OneSlot => 1,
|
||||||
@@ -181,11 +173,7 @@ async fn handle_slots_callback(
|
|||||||
);
|
);
|
||||||
transition_to_field(dialogue, ListingField::StartTime, draft).await?;
|
transition_to_field(dialogue, ListingField::StartTime, draft).await?;
|
||||||
app.bot
|
app.bot
|
||||||
.send_html_message(
|
.send_html_message(response, get_keyboard_for_field(ListingField::StartTime))
|
||||||
target,
|
|
||||||
response,
|
|
||||||
get_keyboard_for_field(ListingField::StartTime),
|
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -196,7 +184,6 @@ async fn handle_start_time_callback(
|
|||||||
dialogue: RootDialogue,
|
dialogue: RootDialogue,
|
||||||
mut draft: ListingDraft,
|
mut draft: ListingDraft,
|
||||||
button: StartTimeKeyboardButtons,
|
button: StartTimeKeyboardButtons,
|
||||||
target: MessageTarget,
|
|
||||||
) -> BotResult {
|
) -> BotResult {
|
||||||
let start_time = match button {
|
let start_time = match button {
|
||||||
StartTimeKeyboardButtons::Now => ListingDuration::zero(),
|
StartTimeKeyboardButtons::Now => ListingDuration::zero(),
|
||||||
@@ -216,11 +203,7 @@ async fn handle_start_time_callback(
|
|||||||
);
|
);
|
||||||
transition_to_field(dialogue, ListingField::EndTime, draft).await?;
|
transition_to_field(dialogue, ListingField::EndTime, draft).await?;
|
||||||
app.bot
|
app.bot
|
||||||
.send_html_message(
|
.send_html_message(response, get_keyboard_for_field(ListingField::EndTime))
|
||||||
target,
|
|
||||||
response,
|
|
||||||
get_keyboard_for_field(ListingField::EndTime),
|
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -231,7 +214,6 @@ async fn handle_duration_callback(
|
|||||||
dialogue: RootDialogue,
|
dialogue: RootDialogue,
|
||||||
mut draft: ListingDraft,
|
mut draft: ListingDraft,
|
||||||
button: DurationKeyboardButtons,
|
button: DurationKeyboardButtons,
|
||||||
target: MessageTarget,
|
|
||||||
) -> BotResult {
|
) -> BotResult {
|
||||||
let duration = ListingDuration::days(match button {
|
let duration = ListingDuration::days(match button {
|
||||||
DurationKeyboardButtons::OneDay => 1,
|
DurationKeyboardButtons::OneDay => 1,
|
||||||
@@ -248,7 +230,7 @@ async fn handle_duration_callback(
|
|||||||
.map_err(|e| anyhow::anyhow!("Error updating duration: {e:?}"))?;
|
.map_err(|e| anyhow::anyhow!("Error updating duration: {e:?}"))?;
|
||||||
|
|
||||||
let flash = get_success_message(ListingField::EndTime, draft.listing_type());
|
let flash = get_success_message(ListingField::EndTime, draft.listing_type());
|
||||||
enter_confirm_save_listing(app, dialogue, target, draft, Some(flash)).await
|
enter_confirm_save_listing(app, dialogue, draft, Some(flash)).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_starting_bid_amount_callback(
|
async fn handle_starting_bid_amount_callback(
|
||||||
@@ -256,7 +238,6 @@ async fn handle_starting_bid_amount_callback(
|
|||||||
dialogue: RootDialogue,
|
dialogue: RootDialogue,
|
||||||
mut draft: ListingDraft,
|
mut draft: ListingDraft,
|
||||||
button: EditMinimumBidIncrementKeyboardButtons,
|
button: EditMinimumBidIncrementKeyboardButtons,
|
||||||
target: MessageTarget,
|
|
||||||
) -> BotResult {
|
) -> BotResult {
|
||||||
let starting_bid_amount = MoneyAmount::from_str(match button {
|
let starting_bid_amount = MoneyAmount::from_str(match button {
|
||||||
EditMinimumBidIncrementKeyboardButtons::OneDollar => "1.00",
|
EditMinimumBidIncrementKeyboardButtons::OneDollar => "1.00",
|
||||||
@@ -273,7 +254,7 @@ async fn handle_starting_bid_amount_callback(
|
|||||||
.map_err(|e| anyhow::anyhow!("Error updating starting bid amount: {e:?}"))?;
|
.map_err(|e| anyhow::anyhow!("Error updating starting bid amount: {e:?}"))?;
|
||||||
|
|
||||||
let flash = get_success_message(ListingField::StartingBidAmount, draft.listing_type());
|
let flash = get_success_message(ListingField::StartingBidAmount, draft.listing_type());
|
||||||
enter_confirm_save_listing(app, dialogue, target, draft, Some(flash)).await
|
enter_confirm_save_listing(app, dialogue, draft, Some(flash)).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_currency_type_callback(
|
async fn handle_currency_type_callback(
|
||||||
@@ -281,7 +262,6 @@ async fn handle_currency_type_callback(
|
|||||||
dialogue: RootDialogue,
|
dialogue: RootDialogue,
|
||||||
mut draft: ListingDraft,
|
mut draft: ListingDraft,
|
||||||
button: CurrencyTypeKeyboardButtons,
|
button: CurrencyTypeKeyboardButtons,
|
||||||
target: MessageTarget,
|
|
||||||
) -> BotResult {
|
) -> BotResult {
|
||||||
let currency_type = match button {
|
let currency_type = match button {
|
||||||
CurrencyTypeKeyboardButtons::Usd => CurrencyType::Usd,
|
CurrencyTypeKeyboardButtons::Usd => CurrencyType::Usd,
|
||||||
@@ -305,14 +285,14 @@ async fn handle_currency_type_callback(
|
|||||||
);
|
);
|
||||||
transition_to_field(dialogue, next_field, draft).await?;
|
transition_to_field(dialogue, next_field, draft).await?;
|
||||||
app.bot
|
app.bot
|
||||||
.send_html_message(target, response, get_keyboard_for_field(next_field))
|
.send_html_message(response, get_keyboard_for_field(next_field))
|
||||||
.await?;
|
.await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cancel the wizard and exit
|
/// Cancel the wizard and exit
|
||||||
pub async fn cancel_wizard(app: App, dialogue: RootDialogue, target: MessageTarget) -> BotResult {
|
pub async fn cancel_wizard(app: App, dialogue: RootDialogue) -> BotResult {
|
||||||
info!("{target:?} cancelled new listing wizard");
|
info!("User cancelled new listing wizard");
|
||||||
enter_select_new_listing_type(app, dialogue, target).await?;
|
enter_select_new_listing_type(app, dialogue).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,20 +35,12 @@ use log::info;
|
|||||||
use teloxide::{prelude::*, types::*};
|
use teloxide::{prelude::*, types::*};
|
||||||
|
|
||||||
/// Handle the /newlisting command - starts the dialogue
|
/// Handle the /newlisting command - starts the dialogue
|
||||||
pub(super) async fn handle_new_listing_command(
|
pub(super) async fn handle_new_listing_command(app: App, dialogue: RootDialogue) -> BotResult {
|
||||||
app: App,
|
enter_select_new_listing_type(app, dialogue).await?;
|
||||||
dialogue: RootDialogue,
|
|
||||||
target: MessageTarget,
|
|
||||||
) -> BotResult {
|
|
||||||
enter_select_new_listing_type(app, dialogue, target).await?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn enter_select_new_listing_type(
|
pub async fn enter_select_new_listing_type(app: App, dialogue: RootDialogue) -> BotResult {
|
||||||
app: App,
|
|
||||||
dialogue: RootDialogue,
|
|
||||||
target: MessageTarget,
|
|
||||||
) -> BotResult {
|
|
||||||
// Initialize the dialogue to listing type selection state
|
// Initialize the dialogue to listing type selection state
|
||||||
dialogue
|
dialogue
|
||||||
.update(NewListingState::SelectingListingType)
|
.update(NewListingState::SelectingListingType)
|
||||||
@@ -57,7 +49,6 @@ pub async fn enter_select_new_listing_type(
|
|||||||
|
|
||||||
app.bot
|
app.bot
|
||||||
.send_html_message(
|
.send_html_message(
|
||||||
target,
|
|
||||||
get_listing_type_selection_message().to_string(),
|
get_listing_type_selection_message().to_string(),
|
||||||
Some(get_listing_type_keyboard()),
|
Some(get_listing_type_keyboard()),
|
||||||
)
|
)
|
||||||
@@ -71,10 +62,9 @@ pub async fn handle_awaiting_draft_field_input(
|
|||||||
dialogue: RootDialogue,
|
dialogue: RootDialogue,
|
||||||
field: ListingField,
|
field: ListingField,
|
||||||
mut draft: ListingDraft,
|
mut draft: ListingDraft,
|
||||||
target: MessageTarget,
|
|
||||||
msg: Message,
|
msg: Message,
|
||||||
) -> BotResult {
|
) -> BotResult {
|
||||||
info!("User {target:?} entered input step: {field:?}");
|
info!("User entered input step: {field:?}");
|
||||||
|
|
||||||
// Process the field update
|
// Process the field update
|
||||||
match update_field_on_draft(field, &mut draft, msg.text()) {
|
match update_field_on_draft(field, &mut draft, msg.text()) {
|
||||||
@@ -99,11 +89,11 @@ pub async fn handle_awaiting_draft_field_input(
|
|||||||
);
|
);
|
||||||
transition_to_field(dialogue, next_field, draft).await?;
|
transition_to_field(dialogue, next_field, draft).await?;
|
||||||
app.bot
|
app.bot
|
||||||
.send_html_message(target, response, get_keyboard_for_field(next_field))
|
.send_html_message(response, get_keyboard_for_field(next_field))
|
||||||
.await?;
|
.await?;
|
||||||
} else {
|
} else {
|
||||||
// Final step - go to confirmation
|
// Final step - go to confirmation
|
||||||
enter_confirm_save_listing(app, dialogue, target, draft, None).await?;
|
enter_confirm_save_listing(app, dialogue, draft, None).await?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -114,10 +104,9 @@ pub async fn handle_editing_field_input(
|
|||||||
dialogue: RootDialogue,
|
dialogue: RootDialogue,
|
||||||
field: ListingField,
|
field: ListingField,
|
||||||
mut draft: ListingDraft,
|
mut draft: ListingDraft,
|
||||||
target: MessageTarget,
|
|
||||||
msg: Message,
|
msg: Message,
|
||||||
) -> BotResult {
|
) -> BotResult {
|
||||||
info!("User {target:?} editing field {field:?}");
|
info!("User editing field {field:?}");
|
||||||
|
|
||||||
// Process the field update
|
// Process the field update
|
||||||
match update_field_on_draft(field, &mut draft, msg.text()) {
|
match update_field_on_draft(field, &mut draft, msg.text()) {
|
||||||
@@ -134,7 +123,7 @@ pub async fn handle_editing_field_input(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let flash = get_edit_success_message(field, draft.listing_type());
|
let flash = get_edit_success_message(field, draft.listing_type());
|
||||||
enter_edit_listing_draft(app, target, draft, dialogue, Some(flash)).await?;
|
enter_edit_listing_draft(app, draft, dialogue, Some(flash)).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,37 +134,36 @@ pub async fn handle_viewing_draft_callback(
|
|||||||
draft: ListingDraft,
|
draft: ListingDraft,
|
||||||
user: PersistedUser,
|
user: PersistedUser,
|
||||||
callback_query: CallbackQuery,
|
callback_query: CallbackQuery,
|
||||||
target: MessageTarget,
|
|
||||||
) -> BotResult {
|
) -> BotResult {
|
||||||
let data = extract_callback_data(&app.bot, callback_query).await?;
|
let data = extract_callback_data(&app.bot, callback_query).await?;
|
||||||
|
|
||||||
match ConfirmationKeyboardButtons::try_from(data.as_str())? {
|
match ConfirmationKeyboardButtons::try_from(data.as_str())? {
|
||||||
ConfirmationKeyboardButtons::Create | ConfirmationKeyboardButtons::Save => {
|
ConfirmationKeyboardButtons::Create | ConfirmationKeyboardButtons::Save => {
|
||||||
info!("User {target:?} confirmed listing creation");
|
info!("User confirmed listing creation");
|
||||||
let success_message = save_listing(&app.daos.listing, draft).await?;
|
let success_message = save_listing(&app.daos.listing, draft).await?;
|
||||||
enter_my_listings(app, dialogue, user, target, Some(success_message)).await?;
|
enter_my_listings(app, dialogue, user, Some(success_message)).await?;
|
||||||
}
|
}
|
||||||
ConfirmationKeyboardButtons::Cancel => {
|
ConfirmationKeyboardButtons::Cancel => {
|
||||||
info!("User {target:?} cancelled listing update");
|
info!("User cancelled listing update");
|
||||||
let response = "🗑️ <b>Changes Discarded</b>\n\n\
|
let response = "🗑️ <b>Changes Discarded</b>\n\n\
|
||||||
Your changes have been discarded and not saved."
|
Your changes have been discarded and not saved."
|
||||||
.to_string();
|
.to_string();
|
||||||
app.bot.send_html_message(target, response, None).await?;
|
app.bot.send_html_message(response, None).await?;
|
||||||
dialogue.exit().await.context("failed to exit dialogue")?;
|
dialogue.exit().await.context("failed to exit dialogue")?;
|
||||||
}
|
}
|
||||||
ConfirmationKeyboardButtons::Discard => {
|
ConfirmationKeyboardButtons::Discard => {
|
||||||
info!("User {target:?} discarded listing creation");
|
info!("User discarded listing creation");
|
||||||
|
|
||||||
let response = "🗑️ <b>Listing Discarded</b>\n\n\
|
let response = "🗑️ <b>Listing Discarded</b>\n\n\
|
||||||
Your listing has been discarded and not created.\n\
|
Your listing has been discarded and not created.\n\
|
||||||
You can start a new listing anytime with /newlisting."
|
You can start a new listing anytime with /newlisting."
|
||||||
.to_string();
|
.to_string();
|
||||||
app.bot.send_html_message(target, response, None).await?;
|
app.bot.send_html_message(response, None).await?;
|
||||||
dialogue.exit().await.context("failed to exit dialogue")?;
|
dialogue.exit().await.context("failed to exit dialogue")?;
|
||||||
}
|
}
|
||||||
ConfirmationKeyboardButtons::Edit => {
|
ConfirmationKeyboardButtons::Edit => {
|
||||||
info!("User {target:?} chose to edit listing");
|
info!("User chose to edit listing");
|
||||||
enter_edit_listing_draft(app, target, draft, dialogue, None).await?;
|
enter_edit_listing_draft(app, draft, dialogue, None).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,14 +176,13 @@ pub async fn handle_editing_draft_callback(
|
|||||||
draft: ListingDraft,
|
draft: ListingDraft,
|
||||||
dialogue: RootDialogue,
|
dialogue: RootDialogue,
|
||||||
callback_query: CallbackQuery,
|
callback_query: CallbackQuery,
|
||||||
target: MessageTarget,
|
|
||||||
) -> BotResult {
|
) -> BotResult {
|
||||||
let data = extract_callback_data(&app.bot, callback_query).await?;
|
let data = extract_callback_data(&app.bot, callback_query).await?;
|
||||||
info!("User {target:?} in editing screen, showing field selection");
|
info!("User in editing screen, showing field selection");
|
||||||
|
|
||||||
let button = FieldSelectionKeyboardButtons::try_from(data.as_str())?;
|
let button = FieldSelectionKeyboardButtons::try_from(data.as_str())?;
|
||||||
if button == FieldSelectionKeyboardButtons::Done {
|
if button == FieldSelectionKeyboardButtons::Done {
|
||||||
return enter_confirm_save_listing(app, dialogue, target, draft, None).await;
|
return enter_confirm_save_listing(app, dialogue, draft, None).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
let field = match button {
|
let field = match button {
|
||||||
@@ -221,9 +208,7 @@ pub async fn handle_editing_draft_callback(
|
|||||||
.context("failed to update dialogue")?;
|
.context("failed to update dialogue")?;
|
||||||
|
|
||||||
let response = format!("Editing {field:?}\n\nPrevious value: {value}");
|
let response = format!("Editing {field:?}\n\nPrevious value: {value}");
|
||||||
app.bot
|
app.bot.send_html_message(response, Some(keyboard)).await?;
|
||||||
.send_html_message(target, response, Some(keyboard))
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -235,19 +220,18 @@ pub async fn handle_editing_draft_field_callback(
|
|||||||
field: ListingField,
|
field: ListingField,
|
||||||
draft: ListingDraft,
|
draft: ListingDraft,
|
||||||
callback_query: CallbackQuery,
|
callback_query: CallbackQuery,
|
||||||
target: MessageTarget,
|
|
||||||
) -> BotResult {
|
) -> BotResult {
|
||||||
let data = extract_callback_data(&app.bot, callback_query).await?;
|
let data = extract_callback_data(&app.bot, callback_query).await?;
|
||||||
info!("User {target:?} editing field: {field:?} -> {data:?}");
|
info!("User editing field: {field:?} -> {data:?}");
|
||||||
|
|
||||||
if data == "edit_back" {
|
if data == "edit_back" {
|
||||||
enter_edit_listing_draft(app, target, draft, dialogue, None).await?;
|
enter_edit_listing_draft(app, draft, dialogue, None).await?;
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// This callback handler typically receives button presses, not text input
|
// This callback handler typically receives button presses, not text input
|
||||||
// For now, just redirect back to edit screen since callback data isn't suitable for validation
|
// For now, just redirect back to edit screen since callback data isn't suitable for validation
|
||||||
enter_edit_listing_draft(app, target, draft, dialogue, None).await?;
|
enter_edit_listing_draft(app, draft, dialogue, None).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -255,14 +239,12 @@ pub async fn handle_editing_draft_field_callback(
|
|||||||
/// Enter the edit listing draft screen
|
/// Enter the edit listing draft screen
|
||||||
pub async fn enter_edit_listing_draft(
|
pub async fn enter_edit_listing_draft(
|
||||||
app: App,
|
app: App,
|
||||||
target: MessageTarget,
|
|
||||||
draft: ListingDraft,
|
draft: ListingDraft,
|
||||||
dialogue: RootDialogue,
|
dialogue: RootDialogue,
|
||||||
flash_message: Option<String>,
|
flash_message: Option<String>,
|
||||||
) -> BotResult {
|
) -> BotResult {
|
||||||
display_listing_summary(
|
display_listing_summary(
|
||||||
app,
|
app,
|
||||||
target,
|
|
||||||
&draft,
|
&draft,
|
||||||
Some(FieldSelectionKeyboardButtons::to_keyboard()),
|
Some(FieldSelectionKeyboardButtons::to_keyboard()),
|
||||||
flash_message,
|
flash_message,
|
||||||
|
|||||||
@@ -9,14 +9,13 @@ use crate::commands::new_listing::NewListingState;
|
|||||||
use crate::db::ListingType;
|
use crate::db::ListingType;
|
||||||
use crate::App;
|
use crate::App;
|
||||||
use crate::RootDialogue;
|
use crate::RootDialogue;
|
||||||
use crate::{commands::new_listing::types::ListingDraft, message_utils::*, BotResult};
|
use crate::{commands::new_listing::types::ListingDraft, BotResult};
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use teloxide::types::InlineKeyboardMarkup;
|
use teloxide::types::InlineKeyboardMarkup;
|
||||||
|
|
||||||
/// Display the listing summary with optional flash message and keyboard
|
/// Display the listing summary with optional flash message and keyboard
|
||||||
pub async fn display_listing_summary(
|
pub async fn display_listing_summary(
|
||||||
app: App,
|
app: App,
|
||||||
target: MessageTarget,
|
|
||||||
draft: &ListingDraft,
|
draft: &ListingDraft,
|
||||||
keyboard: Option<InlineKeyboardMarkup>,
|
keyboard: Option<InlineKeyboardMarkup>,
|
||||||
flash_message: Option<String>,
|
flash_message: Option<String>,
|
||||||
@@ -50,7 +49,7 @@ pub async fn display_listing_summary(
|
|||||||
response_lines.push("Edit your listing:".to_string());
|
response_lines.push("Edit your listing:".to_string());
|
||||||
|
|
||||||
app.bot
|
app.bot
|
||||||
.send_html_message(target, response_lines.join("\n"), keyboard)
|
.send_html_message(response_lines.join("\n"), keyboard)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -60,7 +59,6 @@ pub async fn display_listing_summary(
|
|||||||
pub async fn enter_confirm_save_listing(
|
pub async fn enter_confirm_save_listing(
|
||||||
app: App,
|
app: App,
|
||||||
dialogue: RootDialogue,
|
dialogue: RootDialogue,
|
||||||
target: MessageTarget,
|
|
||||||
draft: ListingDraft,
|
draft: ListingDraft,
|
||||||
flash: Option<String>,
|
flash: Option<String>,
|
||||||
) -> BotResult {
|
) -> BotResult {
|
||||||
@@ -78,7 +76,7 @@ pub async fn enter_confirm_save_listing(
|
|||||||
])
|
])
|
||||||
};
|
};
|
||||||
|
|
||||||
display_listing_summary(app, target, &draft, Some(keyboard), flash).await?;
|
display_listing_summary(app, &draft, Some(keyboard), flash).await?;
|
||||||
dialogue
|
dialogue
|
||||||
.update(NewListingState::ViewingDraft(draft))
|
.update(NewListingState::ViewingDraft(draft))
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
use crate::{message_utils::MessageTarget, App, BotResult};
|
use crate::{App, BotResult};
|
||||||
use log::info;
|
use log::info;
|
||||||
use teloxide::types::Message;
|
use teloxide::types::Message;
|
||||||
|
|
||||||
pub async fn handle_settings(app: App, msg: Message, target: MessageTarget) -> BotResult {
|
pub async fn handle_settings(app: App, msg: Message) -> BotResult {
|
||||||
let response = "⚙️ Settings (Coming Soon)\n\n\
|
let response = "⚙️ Settings (Coming Soon)\n\n\
|
||||||
Here you'll be able to configure:\n\
|
Here you'll be able to configure:\n\
|
||||||
• Notification preferences\n\
|
• Notification preferences\n\
|
||||||
@@ -18,6 +18,6 @@ pub async fn handle_settings(app: App, msg: Message, target: MessageTarget) -> B
|
|||||||
msg.chat.id
|
msg.chat.id
|
||||||
);
|
);
|
||||||
|
|
||||||
app.bot.send_html_message(target, response, None).await?;
|
app.bot.send_html_message(response, None).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,8 @@ use teloxide::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
commands::my_listings::enter_my_listings,
|
commands::my_listings::enter_my_listings, db::user::PersistedUser, keyboard_buttons,
|
||||||
db::user::PersistedUser,
|
message_utils::extract_callback_data, App, BotResult, Command, DialogueRootState, RootDialogue,
|
||||||
keyboard_buttons,
|
|
||||||
message_utils::{extract_callback_data, MessageTarget},
|
|
||||||
App, BotResult, Command, DialogueRootState, RootDialogue,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
keyboard_buttons! {
|
keyboard_buttons! {
|
||||||
@@ -37,19 +34,14 @@ fn get_main_menu_message() -> &'static str {
|
|||||||
Choose an option below to get started! 🚀"
|
Choose an option below to get started! 🚀"
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_start(
|
pub async fn handle_start(app: App, dialogue: RootDialogue, update: Update) -> BotResult {
|
||||||
app: App,
|
|
||||||
dialogue: RootDialogue,
|
|
||||||
target: MessageTarget,
|
|
||||||
update: Update,
|
|
||||||
) -> BotResult {
|
|
||||||
info!("got start message: {update:?}");
|
info!("got start message: {update:?}");
|
||||||
enter_main_menu(app, dialogue, target).await?;
|
enter_main_menu(app, dialogue).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Show the main menu with buttons
|
/// Show the main menu with buttons
|
||||||
pub async fn enter_main_menu(app: App, dialogue: RootDialogue, target: MessageTarget) -> BotResult {
|
pub async fn enter_main_menu(app: App, dialogue: RootDialogue) -> BotResult {
|
||||||
dialogue
|
dialogue
|
||||||
.update(DialogueRootState::MainMenu)
|
.update(DialogueRootState::MainMenu)
|
||||||
.await
|
.await
|
||||||
@@ -57,7 +49,6 @@ pub async fn enter_main_menu(app: App, dialogue: RootDialogue, target: MessageTa
|
|||||||
|
|
||||||
app.bot
|
app.bot
|
||||||
.send_html_message(
|
.send_html_message(
|
||||||
target,
|
|
||||||
get_main_menu_message().to_string(),
|
get_main_menu_message().to_string(),
|
||||||
Some(MainMenuButtons::to_keyboard()),
|
Some(MainMenuButtons::to_keyboard()),
|
||||||
)
|
)
|
||||||
@@ -71,21 +62,19 @@ pub async fn handle_main_menu_callback(
|
|||||||
dialogue: RootDialogue,
|
dialogue: RootDialogue,
|
||||||
user: PersistedUser,
|
user: PersistedUser,
|
||||||
callback_query: CallbackQuery,
|
callback_query: CallbackQuery,
|
||||||
target: MessageTarget,
|
|
||||||
) -> BotResult {
|
) -> BotResult {
|
||||||
let data = extract_callback_data(&app.bot, callback_query).await?;
|
let data = extract_callback_data(&app.bot, callback_query).await?;
|
||||||
info!("User {target:?} selected main menu option: {data:?}");
|
info!("User selected main menu option: {data:?}");
|
||||||
|
|
||||||
let button = MainMenuButtons::try_from(data.as_str())?;
|
let button = MainMenuButtons::try_from(data.as_str())?;
|
||||||
match button {
|
match button {
|
||||||
MainMenuButtons::MyListings => {
|
MainMenuButtons::MyListings => {
|
||||||
// Call show_listings_for_user directly
|
// Call show_listings_for_user directly
|
||||||
enter_my_listings(app, dialogue, user, target, None).await?;
|
enter_my_listings(app, dialogue, user, None).await?;
|
||||||
}
|
}
|
||||||
MainMenuButtons::MyBids => {
|
MainMenuButtons::MyBids => {
|
||||||
app.bot
|
app.bot
|
||||||
.send_html_message(
|
.send_html_message(
|
||||||
target,
|
|
||||||
"💰 <b>My Bids (Coming Soon)</b>\n\n\
|
"💰 <b>My Bids (Coming Soon)</b>\n\n\
|
||||||
Here you'll be able to view:\n\
|
Here you'll be able to view:\n\
|
||||||
• Your active bids\n\
|
• Your active bids\n\
|
||||||
@@ -101,7 +90,6 @@ pub async fn handle_main_menu_callback(
|
|||||||
MainMenuButtons::Settings => {
|
MainMenuButtons::Settings => {
|
||||||
app.bot
|
app.bot
|
||||||
.send_html_message(
|
.send_html_message(
|
||||||
target,
|
|
||||||
"⚙️ <b>Settings (Coming Soon)</b>\n\n\
|
"⚙️ <b>Settings (Coming Soon)</b>\n\n\
|
||||||
Here you'll be able to configure:\n\
|
Here you'll be able to configure:\n\
|
||||||
• Notification preferences\n\
|
• Notification preferences\n\
|
||||||
@@ -122,7 +110,7 @@ pub async fn handle_main_menu_callback(
|
|||||||
Command::descriptions()
|
Command::descriptions()
|
||||||
);
|
);
|
||||||
app.bot
|
app.bot
|
||||||
.send_html_message(target, help_message, Some(MainMenuButtons::to_keyboard()))
|
.send_html_message(help_message, Some(MainMenuButtons::to_keyboard()))
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,13 @@
|
|||||||
use crate::{
|
use crate::{wrap_endpoint, App, BotError, BotResult, WrappedAsyncFn};
|
||||||
message_utils::MessageTarget, wrap_endpoint, App, BotError, BotResult, WrappedAsyncFn,
|
|
||||||
};
|
|
||||||
use futures::future::BoxFuture;
|
use futures::future::BoxFuture;
|
||||||
|
|
||||||
pub async fn handle_error(app: App, target: MessageTarget, error: BotError) -> BotResult {
|
pub async fn handle_error(app: App, error: BotError) -> BotResult {
|
||||||
log::error!("Error in handler: {error:?}");
|
log::error!("Error in handler: {error:?}");
|
||||||
match error {
|
match error {
|
||||||
BotError::UserVisibleError(message) => {
|
BotError::UserVisibleError(message) => app.bot.send_html_message(message, None).await?,
|
||||||
app.bot.send_html_message(target, message, None).await?
|
|
||||||
}
|
|
||||||
BotError::InternalError(_) => {
|
BotError::InternalError(_) => {
|
||||||
app.bot
|
app.bot
|
||||||
.send_html_message(
|
.send_html_message(
|
||||||
target,
|
|
||||||
"An internal error occurred. Please try again later.".to_string(),
|
"An internal error occurred. Please try again later.".to_string(),
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
@@ -22,20 +17,16 @@ pub async fn handle_error(app: App, target: MessageTarget, error: BotError) -> B
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn boxed_handle_error(
|
fn boxed_handle_error(app: App, error: BotError) -> BoxFuture<'static, BotResult> {
|
||||||
app: App,
|
Box::pin(handle_error(app, error))
|
||||||
target: MessageTarget,
|
|
||||||
error: BotError,
|
|
||||||
) -> BoxFuture<'static, BotResult> {
|
|
||||||
Box::pin(handle_error(app, target, error))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type ErrorHandlerWrapped<FnBase, FnBaseArgs> = WrappedAsyncFn<
|
pub type ErrorHandlerWrapped<FnBase, FnBaseArgs> = WrappedAsyncFn<
|
||||||
FnBase,
|
FnBase,
|
||||||
fn(App, MessageTarget, BotError) -> BoxFuture<'static, BotResult>,
|
fn(App, BotError) -> BoxFuture<'static, BotResult>,
|
||||||
BotError,
|
BotError,
|
||||||
FnBaseArgs,
|
FnBaseArgs,
|
||||||
(App, MessageTarget),
|
(App,),
|
||||||
>;
|
>;
|
||||||
|
|
||||||
pub fn with_error_handler<FnBase, FnBaseArgs>(
|
pub fn with_error_handler<FnBase, FnBaseArgs>(
|
||||||
|
|||||||
33
src/main.rs
33
src/main.rs
@@ -25,7 +25,7 @@ use crate::commands::{
|
|||||||
use crate::db::DAOs;
|
use crate::db::DAOs;
|
||||||
use crate::handle_error::with_error_handler;
|
use crate::handle_error::with_error_handler;
|
||||||
use crate::handler_utils::{find_or_create_db_user_from_update, update_into_message_target};
|
use crate::handler_utils::{find_or_create_db_user_from_update, update_into_message_target};
|
||||||
use crate::message_sender::BoxMessageSender;
|
use crate::message_sender::{BotMessageSender, BoxedMessageSender};
|
||||||
use crate::sqlite_storage::SqliteStorage;
|
use crate::sqlite_storage::SqliteStorage;
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
pub use bot_result::*;
|
pub use bot_result::*;
|
||||||
@@ -39,12 +39,12 @@ pub use wrap_endpoint::*;
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct App {
|
pub struct App {
|
||||||
pub bot: Arc<BoxMessageSender>,
|
pub bot: Arc<BoxedMessageSender>,
|
||||||
pub daos: DAOs,
|
pub daos: DAOs,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub fn new(bot: BoxMessageSender, daos: DAOs) -> Self {
|
pub fn new(bot: BoxedMessageSender, daos: DAOs) -> Self {
|
||||||
Self {
|
Self {
|
||||||
bot: Arc::new(bot),
|
bot: Arc::new(bot),
|
||||||
daos,
|
daos,
|
||||||
@@ -100,11 +100,9 @@ type RootDialogue = Dialogue<DialogueRootState, SqliteStorage<Json>>;
|
|||||||
|
|
||||||
pub fn main_handler() -> BotHandler {
|
pub fn main_handler() -> BotHandler {
|
||||||
dptree::entry()
|
dptree::entry()
|
||||||
.map(|app: App| app.daos.clone())
|
|
||||||
.map(|daos: DAOs| daos.user.clone())
|
.map(|daos: DAOs| daos.user.clone())
|
||||||
.map(|daos: DAOs| daos.listing.clone())
|
.map(|daos: DAOs| daos.listing.clone())
|
||||||
.map(|daos: DAOs| daos.bid.clone())
|
.map(|daos: DAOs| daos.bid.clone())
|
||||||
.filter_map(update_into_message_target)
|
|
||||||
.filter_map_async(find_or_create_db_user_from_update)
|
.filter_map_async(find_or_create_db_user_from_update)
|
||||||
.branch(my_listings_inline_handler())
|
.branch(my_listings_inline_handler())
|
||||||
.branch(
|
.branch(
|
||||||
@@ -156,20 +154,21 @@ async fn main() -> Result<()> {
|
|||||||
// Set up the bot's command menu
|
// Set up the bot's command menu
|
||||||
setup_bot_commands(&bot).await?;
|
setup_bot_commands(&bot).await?;
|
||||||
|
|
||||||
|
let handler_with_deps = dptree::entry()
|
||||||
|
.filter_map(|bot: Box<Bot>, update: Update, daos: DAOs| {
|
||||||
|
let target = update_into_message_target(update)?;
|
||||||
|
Some(App::new(
|
||||||
|
Box::new(BotMessageSender::new(*bot, target)),
|
||||||
|
daos.clone(),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.chain(main_handler());
|
||||||
|
|
||||||
let dialog_storage = SqliteStorage::new(db_pool.clone(), Json).await?;
|
let dialog_storage = SqliteStorage::new(db_pool.clone(), Json).await?;
|
||||||
let daos = DAOs::new(db_pool.clone());
|
let daos = DAOs::new(db_pool.clone());
|
||||||
let app = App::new(bot.clone(), daos.clone());
|
|
||||||
|
|
||||||
// Create dispatcher with dialogue system
|
Dispatcher::builder(bot, handler_with_deps)
|
||||||
Dispatcher::builder(bot, main_handler())
|
.dependencies(dptree::deps![dialog_storage, daos])
|
||||||
.dependencies(dptree::deps![
|
|
||||||
dialog_storage,
|
|
||||||
daos,
|
|
||||||
app.daos.user.clone(),
|
|
||||||
app.daos.listing.clone(),
|
|
||||||
app.daos.bid.clone(),
|
|
||||||
app
|
|
||||||
])
|
|
||||||
.enable_ctrlc_handler()
|
.enable_ctrlc_handler()
|
||||||
.worker_queue_size(1)
|
.worker_queue_size(1)
|
||||||
.build()
|
.build()
|
||||||
@@ -199,7 +198,7 @@ mod tests {
|
|||||||
let mut bot = MockMessageSender::new();
|
let mut bot = MockMessageSender::new();
|
||||||
bot.expect_send_html_message()
|
bot.expect_send_html_message()
|
||||||
.times(1)
|
.times(1)
|
||||||
.returning(|_, _, _| Ok(()));
|
.returning(|_, _| Ok(()));
|
||||||
let deps = create_deps(bot).await;
|
let deps = create_deps(bot).await;
|
||||||
let handler = main_handler();
|
let handler = main_handler();
|
||||||
dptree::type_check(handler.sig(), &deps, &[]);
|
dptree::type_check(handler.sig(), &deps, &[]);
|
||||||
|
|||||||
@@ -14,10 +14,10 @@ use teloxide::{
|
|||||||
pub trait MessageSender {
|
pub trait MessageSender {
|
||||||
async fn send_html_message(
|
async fn send_html_message(
|
||||||
&self,
|
&self,
|
||||||
target: MessageTarget,
|
|
||||||
text: String,
|
text: String,
|
||||||
keyboard: Option<InlineKeyboardMarkup>,
|
keyboard: Option<InlineKeyboardMarkup>,
|
||||||
) -> BotResult;
|
) -> BotResult;
|
||||||
|
fn with_target(&self, target: MessageTarget) -> BoxedMessageSender;
|
||||||
async fn answer_inline_query(
|
async fn answer_inline_query(
|
||||||
&self,
|
&self,
|
||||||
inline_query_id: InlineQueryId,
|
inline_query_id: InlineQueryId,
|
||||||
@@ -27,7 +27,7 @@ pub trait MessageSender {
|
|||||||
async fn get_me(&self) -> BotResult<Me>;
|
async fn get_me(&self) -> BotResult<Me>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type BoxMessageSender = Box<dyn MessageSender + Send + Sync>;
|
pub type BoxedMessageSender = Box<dyn MessageSender + Send + Sync>;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mockall::mock! {
|
mockall::mock! {
|
||||||
@@ -37,9 +37,9 @@ mockall::mock! {
|
|||||||
}
|
}
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl MessageSender for MessageSender {
|
impl MessageSender for MessageSender {
|
||||||
|
fn with_target(&self, target: MessageTarget) -> BoxedMessageSender;
|
||||||
async fn send_html_message(
|
async fn send_html_message(
|
||||||
&self,
|
&self,
|
||||||
target: MessageTarget,
|
|
||||||
text: String,
|
text: String,
|
||||||
keyboard: Option<InlineKeyboardMarkup>,
|
keyboard: Option<InlineKeyboardMarkup>,
|
||||||
) -> BotResult;
|
) -> BotResult;
|
||||||
@@ -53,17 +53,29 @@ mockall::mock! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct BotMessageSender(Bot, MessageTarget);
|
||||||
|
impl BotMessageSender {
|
||||||
|
pub fn new(bot: Bot, message_target: MessageTarget) -> Self {
|
||||||
|
Self(bot, message_target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl MessageSender for Bot {
|
impl MessageSender for BotMessageSender {
|
||||||
|
fn with_target(&self, target: MessageTarget) -> BoxedMessageSender {
|
||||||
|
let clone = Self(self.0.clone(), target);
|
||||||
|
Box::new(clone)
|
||||||
|
}
|
||||||
async fn send_html_message(
|
async fn send_html_message(
|
||||||
&self,
|
&self,
|
||||||
target: MessageTarget,
|
|
||||||
text: String,
|
text: String,
|
||||||
keyboard: Option<InlineKeyboardMarkup>,
|
keyboard: Option<InlineKeyboardMarkup>,
|
||||||
) -> BotResult {
|
) -> BotResult {
|
||||||
|
let target = self.1.clone();
|
||||||
if let Some(message_id) = target.message_id {
|
if let Some(message_id) = target.message_id {
|
||||||
log::info!("Editing message in chat: {target:?}");
|
log::info!("Editing message in chat: {target:?}");
|
||||||
let mut message = self
|
let mut message = self
|
||||||
|
.0
|
||||||
.edit_message_text(target.chat_id, message_id, &text)
|
.edit_message_text(target.chat_id, message_id, &text)
|
||||||
.parse_mode(ParseMode::Html);
|
.parse_mode(ParseMode::Html);
|
||||||
if let Some(kb) = keyboard {
|
if let Some(kb) = keyboard {
|
||||||
@@ -73,6 +85,7 @@ impl MessageSender for Bot {
|
|||||||
} else {
|
} else {
|
||||||
log::info!("Sending message to chat: {target:?}");
|
log::info!("Sending message to chat: {target:?}");
|
||||||
let mut message = self
|
let mut message = self
|
||||||
|
.0
|
||||||
.send_message(target.chat_id, &text)
|
.send_message(target.chat_id, &text)
|
||||||
.parse_mode(ParseMode::Html);
|
.parse_mode(ParseMode::Html);
|
||||||
if let Some(kb) = keyboard {
|
if let Some(kb) = keyboard {
|
||||||
@@ -88,21 +101,24 @@ impl MessageSender for Bot {
|
|||||||
inline_query_id: InlineQueryId,
|
inline_query_id: InlineQueryId,
|
||||||
results: Vec<InlineQueryResult>,
|
results: Vec<InlineQueryResult>,
|
||||||
) -> BotResult {
|
) -> BotResult {
|
||||||
teloxide::prelude::Requester::answer_inline_query(self, inline_query_id, results)
|
self.0
|
||||||
|
.answer_inline_query(inline_query_id, results)
|
||||||
.await
|
.await
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
.map_err(|err| BotError::InternalError(err.into()))
|
.map_err(|err| BotError::InternalError(err.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn answer_callback_query(&self, query_id: CallbackQueryId) -> BotResult {
|
async fn answer_callback_query(&self, query_id: CallbackQueryId) -> BotResult {
|
||||||
teloxide::prelude::Requester::answer_callback_query(self, query_id)
|
self.0
|
||||||
|
.answer_callback_query(query_id)
|
||||||
.await
|
.await
|
||||||
.map(|_| ())
|
.map(|_| ())
|
||||||
.map_err(|err| BotError::InternalError(err.into()))
|
.map_err(|err| BotError::InternalError(err.into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_me(&self) -> BotResult<Me> {
|
async fn get_me(&self) -> BotResult<Me> {
|
||||||
teloxide::prelude::Requester::get_me(self)
|
self.0
|
||||||
|
.get_me()
|
||||||
.await
|
.await
|
||||||
.map_err(|err| BotError::InternalError(err.into()))
|
.map_err(|err| BotError::InternalError(err.into()))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::{message_sender::BoxMessageSender, BotResult};
|
use crate::{message_sender::BoxedMessageSender, BotResult};
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use num::One;
|
use num::One;
|
||||||
@@ -127,7 +127,7 @@ pub fn create_single_button_keyboard(text: &str, callback_data: &str) -> InlineK
|
|||||||
|
|
||||||
// Extract callback data and answer callback query
|
// Extract callback data and answer callback query
|
||||||
pub async fn extract_callback_data(
|
pub async fn extract_callback_data(
|
||||||
bot: &BoxMessageSender,
|
bot: &BoxedMessageSender,
|
||||||
callback_query: CallbackQuery,
|
callback_query: CallbackQuery,
|
||||||
) -> BotResult<String> {
|
) -> BotResult<String> {
|
||||||
let data = match callback_query.data {
|
let data = match callback_query.data {
|
||||||
|
|||||||
@@ -178,7 +178,8 @@ pub async fn create_deps(mock_bot: MockMessageSender) -> DependencyMap {
|
|||||||
let update = create_tele_update();
|
let update = create_tele_update();
|
||||||
let pool = create_test_pool().await;
|
let pool = create_test_pool().await;
|
||||||
let dialog_storage = SqliteStorage::new(pool.clone(), Json).await.unwrap();
|
let dialog_storage = SqliteStorage::new(pool.clone(), Json).await.unwrap();
|
||||||
let app = App::new(Box::new(mock_bot), DAOs::new(pool));
|
let daos = DAOs::new(pool);
|
||||||
|
let app = App::new(Box::new(mock_bot), daos.clone());
|
||||||
let me_user = create_tele_user("me");
|
let me_user = create_tele_user("me");
|
||||||
let me = Me {
|
let me = Me {
|
||||||
user: me_user,
|
user: me_user,
|
||||||
@@ -188,7 +189,7 @@ pub async fn create_deps(mock_bot: MockMessageSender) -> DependencyMap {
|
|||||||
can_connect_to_business: true,
|
can_connect_to_business: true,
|
||||||
has_main_web_app: true,
|
has_main_web_app: true,
|
||||||
};
|
};
|
||||||
dptree::deps![update, dialog_storage, app, me]
|
dptree::deps![update, dialog_storage, app, me, daos]
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
Reference in New Issue
Block a user