283 lines
8.5 KiB
Rust
283 lines
8.5 KiB
Rust
use crate::{
|
|
case,
|
|
commands::{
|
|
enter_main_menu,
|
|
new_listing::{enter_edit_listing_draft, ListingDraft},
|
|
},
|
|
db::{listing::PersistedListing, user::PersistedUser, ListingDAO, ListingDbId, UserDAO},
|
|
keyboard_buttons,
|
|
message_utils::{extract_callback_data, pluralize_with_count, send_message, MessageTarget},
|
|
Command, DialogueRootState, HandlerResult, RootDialogue,
|
|
};
|
|
use serde::{Deserialize, Serialize};
|
|
use sqlx::SqlitePool;
|
|
use teloxide::{
|
|
dispatching::{DpHandlerDescription, UpdateFilterExt},
|
|
prelude::*,
|
|
types::{InlineKeyboardButton, Message},
|
|
Bot,
|
|
};
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
pub enum MyListingsState {
|
|
ViewingListings,
|
|
ManagingListing(ListingDbId),
|
|
}
|
|
impl From<MyListingsState> for DialogueRootState {
|
|
fn from(state: MyListingsState) -> Self {
|
|
DialogueRootState::MyListings(state)
|
|
}
|
|
}
|
|
|
|
keyboard_buttons! {
|
|
enum ManageListingButtons {
|
|
[
|
|
Edit("✏️ Edit", "manage_listing_edit"),
|
|
Delete("🗑️ Delete", "manage_listing_delete"),
|
|
],
|
|
[
|
|
Back("⬅️ Back", "manage_listing_back"),
|
|
]
|
|
}
|
|
}
|
|
|
|
keyboard_buttons! {
|
|
enum MyListingsButtons {
|
|
BackToMenu("⬅️ Back to Menu", "my_listings_back_to_menu"),
|
|
}
|
|
}
|
|
|
|
pub fn my_listings_handler() -> Handler<'static, HandlerResult, DpHandlerDescription> {
|
|
dptree::entry()
|
|
.branch(
|
|
Update::filter_message().filter_command::<Command>().branch(
|
|
dptree::case![Command::MyListings].endpoint(handle_my_listings_command_input),
|
|
),
|
|
)
|
|
.branch(
|
|
Update::filter_callback_query()
|
|
.branch(
|
|
// Callback when user taps a listing ID button to manage that listing
|
|
case![DialogueRootState::MyListings(
|
|
MyListingsState::ViewingListings
|
|
)]
|
|
.endpoint(handle_viewing_listings_callback),
|
|
)
|
|
.branch(
|
|
case![DialogueRootState::MyListings(
|
|
MyListingsState::ManagingListing(listing_id)
|
|
)]
|
|
.endpoint(handle_managing_listing_callback),
|
|
),
|
|
)
|
|
}
|
|
|
|
async fn handle_my_listings_command_input(
|
|
db_pool: SqlitePool,
|
|
bot: Bot,
|
|
dialogue: RootDialogue,
|
|
msg: Message,
|
|
) -> HandlerResult {
|
|
let from = msg.from.unwrap();
|
|
show_listings_for_user(db_pool, dialogue, bot, from.id, msg.chat).await?;
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn show_listings_for_user(
|
|
db_pool: SqlitePool,
|
|
dialogue: RootDialogue,
|
|
bot: Bot,
|
|
user: teloxide::types::UserId,
|
|
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()]]);
|
|
|
|
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!",
|
|
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\
|
|
Select a listing to view details",
|
|
pluralize_with_count(listings.len(), "listing", "listings")
|
|
);
|
|
|
|
send_message(&bot, target, response, Some(keyboard)).await?;
|
|
Ok(())
|
|
}
|
|
|
|
async fn handle_viewing_listings_callback(
|
|
db_pool: SqlitePool,
|
|
bot: Bot,
|
|
dialogue: RootDialogue,
|
|
callback_query: CallbackQuery,
|
|
) -> 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(());
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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(())
|
|
}
|
|
|
|
async fn show_listing_details(
|
|
bot: &Bot,
|
|
listing: PersistedListing,
|
|
target: impl Into<MessageTarget>,
|
|
) -> HandlerResult {
|
|
let response = format!(
|
|
"🔍 <b>Listing Details</b>\n\n\
|
|
<b>Title:</b> {}\n\
|
|
<b>Description:</b> {}\n",
|
|
listing.base.title,
|
|
listing
|
|
.base
|
|
.description
|
|
.as_deref()
|
|
.unwrap_or("No description"),
|
|
);
|
|
|
|
send_message(
|
|
bot,
|
|
target,
|
|
response,
|
|
Some(ManageListingButtons::to_keyboard()),
|
|
)
|
|
.await?;
|
|
Ok(())
|
|
}
|
|
|
|
async fn handle_managing_listing_callback(
|
|
db_pool: SqlitePool,
|
|
bot: Bot,
|
|
dialogue: RootDialogue,
|
|
callback_query: CallbackQuery,
|
|
listing_id: ListingDbId,
|
|
) -> HandlerResult {
|
|
let (data, from, message_id) = extract_callback_data(&bot, callback_query).await?;
|
|
let target = (from.clone(), message_id);
|
|
|
|
match ManageListingButtons::try_from(data.as_str())? {
|
|
ManageListingButtons::Edit => {
|
|
let (_, listing) =
|
|
get_user_and_listing(&db_pool, &bot, from.id, listing_id, target.clone()).await?;
|
|
let draft = ListingDraft::from_persisted(listing);
|
|
enter_edit_listing_draft(&bot, target, draft, dialogue, None).await?;
|
|
}
|
|
ManageListingButtons::Delete => {
|
|
ListingDAO::delete_listing(&db_pool, listing_id).await?;
|
|
send_message(&bot, target, "Listing deleted.", None).await?;
|
|
}
|
|
ManageListingButtons::Back => {
|
|
dialogue.update(MyListingsState::ViewingListings).await?;
|
|
show_listings_for_user(db_pool, dialogue, bot, from.id, target).await?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn get_user_and_listing(
|
|
db_pool: &SqlitePool,
|
|
bot: &Bot,
|
|
user_id: teloxide::types::UserId,
|
|
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"));
|
|
}
|
|
};
|
|
|
|
let listing = match ListingDAO::find_by_id(db_pool, listing_id).await? {
|
|
Some(listing) => listing,
|
|
None => {
|
|
send_message(bot, target, "❌ Listing not found.", None).await?;
|
|
return Err(anyhow::anyhow!("Listing not found"));
|
|
}
|
|
};
|
|
|
|
if listing.base.seller_id != user.persisted.id {
|
|
send_message(
|
|
bot,
|
|
target,
|
|
"❌ You can only manage your own auctions.",
|
|
None,
|
|
)
|
|
.await?;
|
|
return Err(anyhow::anyhow!("User does not own listing"));
|
|
}
|
|
|
|
Ok((user, listing))
|
|
}
|