From 0f26c9bdba76d52bf6b7cd2b8a1f63a98d8bb0da Mon Sep 17 00:00:00 2001 From: machka-pasla Date: Sun, 3 Aug 2025 17:51:37 +0300 Subject: [PATCH] Implement user subscription deletion and banned users management - Replaced the trial reset functionality to delete all user subscriptions instead of deactivating them, ensuring a complete reset of trial eligibility. - Added a new method to retrieve banned users from the database, enhancing the admin's ability to manage user bans. - Updated user management handlers to utilize the new subscription deletion and banned users retrieval features. - Enhanced localization files to support new messages related to banned users. --- bot/handlers/admin/user_management.py | 61 ++++++++++++++++++++++++--- db/dal/subscription_dal.py | 12 ++++++ db/dal/user_dal.py | 11 +++++ locales/en.json | 26 +++++++++++- locales/ru.json | 26 +++++++++++- 5 files changed, 128 insertions(+), 8 deletions(-) diff --git a/bot/handlers/admin/user_management.py b/bot/handlers/admin/user_management.py index 16e7b2e..21032f5 100644 --- a/bot/handlers/admin/user_management.py +++ b/bot/handlers/admin/user_management.py @@ -291,8 +291,8 @@ async def handle_reset_trial(callback: types.CallbackQuery, user: User, _ = lambda key, **kwargs: i18n_instance.gettext(lang, key, **kwargs) try: - # Deactivate all user subscriptions - await subscription_dal.deactivate_all_user_subscriptions(session, user.user_id) + # Delete all user subscriptions to reset trial eligibility + await subscription_dal.delete_all_user_subscriptions(session, user.user_id) await session.commit() await callback.answer(_( @@ -362,7 +362,10 @@ async def handle_toggle_ban(callback: types.CallbackQuery, user: User, # Refresh user card with updated ban status user.is_banned = new_ban_status # Update local object - await handle_refresh_user_card(callback, user, None, session, i18n_instance, lang) + from config.settings import Settings + settings = Settings() + subscription_service = SubscriptionService(settings) + await handle_refresh_user_card(callback, user, subscription_service, session, i18n_instance, lang) except Exception as e: logging.error(f"Error toggling ban for user {user.user_id}: {e}") @@ -624,8 +627,9 @@ async def process_direct_message_handler(message: types.Message, state: FSMConte user_id=target_user_id )) - # Show user card again - user_card_text = await format_user_card(target_user, session, None, i18n, current_lang) + # Show user card again + subscription_service = SubscriptionService(settings) + user_card_text = await format_user_card(target_user, session, subscription_service, i18n, current_lang) keyboard = get_user_card_keyboard(target_user.user_id, i18n, current_lang) await message.answer( @@ -641,4 +645,49 @@ async def process_direct_message_handler(message: types.Message, state: FSMConte default="❌ Ошибка отправки сообщения" )) - await state.clear() \ No newline at end of file + await state.clear() + + +async def view_banned_users_handler(callback: types.CallbackQuery, + state: FSMContext, i18n_data: dict, + settings: Settings, session: AsyncSession): + """Display list of banned users""" + current_lang = i18n_data.get("current_language", settings.DEFAULT_LANGUAGE) + i18n: Optional[JsonI18n] = i18n_data.get("i18n_instance") + if not i18n or not callback.message: + await callback.answer("Error preparing banned users list.", show_alert=True) + return + _ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs) + + try: + # Get banned users + banned_users = await user_dal.get_banned_users(session) + + if not banned_users: + message_text = _( + "admin_banned_users_empty", + default="📋 Заблокированные пользователи\n\nСписок пуст" + ) + else: + user_list = [] + for user in banned_users: + display_name = user.telegram_first_name or "Unknown" + if user.telegram_username: + display_name = f"@{user.telegram_username}" + user_list.append(f"• {display_name} (ID: {user.user_id})") + + message_text = _( + "admin_banned_users_list", + default="📋 Заблокированные пользователи ({count}):\n\n{users}", + count=len(banned_users), + users="\n".join(user_list) + ) + + await callback.message.edit_text( + message_text, + reply_markup=get_back_to_admin_panel_keyboard(current_lang, i18n) + ) + + except Exception as e: + logging.error(f"Error displaying banned users: {e}") + await callback.answer("Error loading banned users", show_alert=True) \ No newline at end of file diff --git a/db/dal/subscription_dal.py b/db/dal/subscription_dal.py index 105303c..66d4cf4 100644 --- a/db/dal/subscription_dal.py +++ b/db/dal/subscription_dal.py @@ -144,6 +144,18 @@ async def deactivate_all_user_subscriptions( return result.rowcount +async def delete_all_user_subscriptions( + session: AsyncSession, user_id: int) -> int: + """Completely delete all user subscriptions (for trial reset)""" + stmt = delete(Subscription).where(Subscription.user_id == user_id) + result = await session.execute(stmt) + if result.rowcount > 0: + logging.info( + f"Deleted {result.rowcount} subscription records for user {user_id} for trial reset." + ) + return result.rowcount + + async def update_subscription_end_date( session: AsyncSession, subscription_id: int, new_end_date: datetime) -> Optional[Subscription]: diff --git a/db/dal/user_dal.py b/db/dal/user_dal.py index 8278b53..b661fff 100644 --- a/db/dal/user_dal.py +++ b/db/dal/user_dal.py @@ -93,6 +93,17 @@ async def set_user_ban_status( return False +async def get_banned_users(session: AsyncSession) -> List[User]: + """Get all banned users""" + stmt = ( + select(User) + .where(User.is_banned == True) + .order_by(User.registration_date.desc()) + ) + result = await session.execute(stmt) + return result.scalars().all() + + async def get_banned_users_paginated( session: AsyncSession, limit: int, offset: int ) -> Tuple[List[User], int]: diff --git a/locales/en.json b/locales/en.json index 28728ca..9290905 100644 --- a/locales/en.json +++ b/locales/en.json @@ -299,5 +299,29 @@ "log_new_user_registration": "👤 New User\n\n🆔 ID: {user_id}\n👤 Name: {user_display}{referral_text}\n📅 Time: {timestamp}", "log_payment_received": "{provider_emoji} Payment Received\n\n👤 User: {user_display}\n💰 Amount: {amount} {currency}\n📅 Period: {months} mo.\n🏦 Provider: {payment_provider}\n🕐 Time: {timestamp}", "log_promo_activation": "🎁 Promo Code Activated\n\n👤 User: {user_display}\n🏷 Code: {promo_code}\n🎯 Bonus: +{bonus_days}d\n🕐 Time: {timestamp}", - "log_trial_activation": "🆓 Trial Activated\n\n👤 User: {user_display}\n⏰ Valid until: {end_date}\n🕐 Time: {timestamp}" + "log_trial_activation": "🆓 Trial Activated\n\n👤 User: {user_display}\n⏰ Valid until: {end_date}\n🕐 Time: {timestamp}", + + "admin_general_cancel_operation": "Operation cancelled ❌", + "admin_promo_codes_menu_bulk_create": "📦 Bulk Creation", + "admin_bulk_promo_create_prompt": "📦 Bulk Promo Code Creation\n\nEnter data in format:\nquantity|days|description\n\nExample:\n10|7|Weekly promo", + "admin_bulk_promo_invalid_format": "❌ Invalid format. Use: quantity|days|description", + "admin_bulk_promo_created_success": "✅ Created {count} promo codes for {days} days!\n\nDescription: {description}\nCodes: {codes}", + + "admin_logs_export_csv": "📄 Export to CSV", + "admin_logs_csv_export_started": "📄 Starting log export to CSV...", + "admin_logs_csv_export_success": "✅ Logs exported! File attached above.", + + "admin_csv_header_log_id": "Log ID", + "admin_csv_header_timestamp": "Timestamp", + "admin_csv_header_user_id": "User ID", + "admin_csv_header_telegram_username": "Username", + "admin_csv_header_telegram_first_name": "First Name", + "admin_csv_header_event_type": "Event Type", + "admin_csv_header_content": "Content", + "admin_csv_header_is_admin_event": "Admin Event", + "admin_csv_header_target_user_id": "Target User ID", + "admin_csv_header_raw_update_preview": "Update Preview", + + "admin_banned_users_empty": "📋 Banned Users\n\nList is empty", + "admin_banned_users_list": "📋 Banned Users ({count}):\n\n{users}" } diff --git a/locales/ru.json b/locales/ru.json index 729d091..220cf96 100644 --- a/locales/ru.json +++ b/locales/ru.json @@ -299,5 +299,29 @@ "log_new_user_registration": "👤 Новый пользователь\n\n🆔 ID: {user_id}\n👤 Имя: {user_display}{referral_text}\n📅 Время: {timestamp}", "log_payment_received": "{provider_emoji} Получен платеж\n\n👤 Пользователь: {user_display}\n💰 Сумма: {amount} {currency}\n📅 Период: {months} мес.\n🏦 Провайдер: {payment_provider}\n🕐 Время: {timestamp}", "log_promo_activation": "🎁 Активирован промокод\n\n👤 Пользователь: {user_display}\n🏷 Код: {promo_code}\n🎯 Бонус: +{bonus_days} дн.\n🕐 Время: {timestamp}", - "log_trial_activation": "🆓 Активирован триал\n\n👤 Пользователь: {user_display}\n⏰ Действует до: {end_date}\n🕐 Время: {timestamp}" + "log_trial_activation": "🆓 Активирован триал\n\n👤 Пользователь: {user_display}\n⏰ Действует до: {end_date}\n🕐 Время: {timestamp}", + + "admin_general_cancel_operation": "Операция отменена ❌", + "admin_promo_codes_menu_bulk_create": "📦 Массовое создание", + "admin_bulk_promo_create_prompt": "📦 Массовое создание промокодов\n\nВведите данные в формате:\nколичество|дни|описание\n\nПример:\n10|7|Промо на неделю", + "admin_bulk_promo_invalid_format": "❌ Неверный формат. Используйте: количество|дни|описание", + "admin_bulk_promo_created_success": "✅ Создано {count} промокодов на {days} дней!\n\nОписание: {description}\nПромокоды: {codes}", + + "admin_logs_export_csv": "📄 Экспорт в CSV", + "admin_logs_csv_export_started": "📄 Начинаю экспорт логов в CSV...", + "admin_logs_csv_export_success": "✅ Логи экспортированы! Файл прикреплен выше.", + + "admin_csv_header_log_id": "ID Лога", + "admin_csv_header_timestamp": "Время", + "admin_csv_header_user_id": "ID Пользователя", + "admin_csv_header_telegram_username": "Username", + "admin_csv_header_telegram_first_name": "Имя", + "admin_csv_header_event_type": "Тип события", + "admin_csv_header_content": "Содержание", + "admin_csv_header_is_admin_event": "Админ событие", + "admin_csv_header_target_user_id": "ID Целевого юзера", + "admin_csv_header_raw_update_preview": "Превью обновления", + + "admin_banned_users_empty": "📋 Заблокированные пользователи\n\nСписок пуст", + "admin_banned_users_list": "📋 Заблокированные пользователи ({count}):\n\n{users}" }