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