From 97d598a2938bc10331ea3a3dec43bd1117076726 Mon Sep 17 00:00:00 2001 From: hteppl Date: Sat, 11 Jan 2025 02:24:16 +0300 Subject: [PATCH] Implement admin stats handler --- handlers/admin/admin_stats.py | 178 ++++++++++++++++++++++++++++++++++ keyboards/admin/stats_kb.py | 24 +++++ 2 files changed, 202 insertions(+) create mode 100644 handlers/admin/admin_stats.py create mode 100644 keyboards/admin/stats_kb.py diff --git a/handlers/admin/admin_stats.py b/handlers/admin/admin_stats.py new file mode 100644 index 00000000..113a6caa --- /dev/null +++ b/handlers/admin/admin_stats.py @@ -0,0 +1,178 @@ +from datetime import datetime +from io import BytesIO +from typing import Any + +from aiogram import F, Router +from aiogram.types import CallbackQuery, BufferedInputFile + +from filters.admin import IsAdminFilter +from keyboards.admin.panel_kb import AdminPanelCallback, build_admin_back_kb +from keyboards.admin.stats_kb import build_stats_kb +from logger import logger + +router = Router() + + +@router.callback_query( + AdminPanelCallback.filter(F.action == "stats"), + IsAdminFilter(), +) +async def user_stats_menu(callback_query: CallbackQuery, session: Any): + try: + total_users = await session.fetchval("SELECT COUNT(*) FROM connections") + total_keys = await session.fetchval("SELECT COUNT(*) FROM keys") + total_referrals = await session.fetchval("SELECT COUNT(*) FROM referrals") + + total_payments_today = await session.fetchval( + "SELECT COALESCE(SUM(amount), 0) FROM payments WHERE created_at >= CURRENT_DATE" + ) + total_payments_week = await session.fetchval( + "SELECT COALESCE(SUM(amount), 0) FROM payments WHERE created_at >= date_trunc('week', CURRENT_DATE)" + ) + total_payments_month = await session.fetchval( + "SELECT COALESCE(SUM(amount), 0) FROM payments WHERE created_at >= date_trunc('month', CURRENT_DATE)" + ) + total_payments_all_time = await session.fetchval( + "SELECT COALESCE(SUM(amount), 0) FROM payments" + ) + + active_keys = await session.fetchval( + "SELECT COUNT(*) FROM keys WHERE expiry_time > $1", + int(datetime.utcnow().timestamp() * 1000), + ) + expired_keys = total_keys - active_keys + + stats_message = ( + f"📊 Подробная статистика проекта:\n\n" + f"👥 Пользователи:\n" + f" 🌐 Зарегистрировано: {total_users}\n" + f" 🤝 Привлеченных рефералов: {total_referrals}\n\n" + f"🔑 Ключи:\n" + f" 🌈 Всего сгенерировано: {total_keys}\n" + f" ✅ Действующих: {active_keys}\n" + f" ❌ Просроченных: {expired_keys}\n\n" + f"💰 Финансовая статистика:\n" + f" 📅 За день: {total_payments_today} ₽\n" + f" 📆 За неделю: {total_payments_week} ₽\n" + f" 📆 За месяц: {total_payments_month} ₽\n" + f" 🏦 За все время: {total_payments_all_time} ₽\n" + ) + + kb = build_stats_kb() + + await callback_query.message.edit_text( + text=stats_message, + reply_markup=kb + ) + except Exception as e: + logger.error(f"Error in user_stats_menu: {e}") + + +@router.callback_query( + AdminPanelCallback.filter(F.action == "stats_export_users_csv"), + IsAdminFilter(), +) +async def export_users_csv(callback_query: CallbackQuery, session: Any): + kb = build_admin_back_kb("stats") + + try: + users = await session.fetch( + """ + SELECT + u.tg_id, + u.username, + u.first_name, + u.last_name, + u.language_code, + u.is_bot, + c.balance, + c.trial + FROM users u + LEFT JOIN connections c ON u.tg_id = c.tg_id + """ + ) + + if not users: + await callback_query.message.answer( + text="📭 Нет пользователей для экспорта.", + reply_markup=kb + ) + return + + csv_data = "tg_id,username,first_name,last_name,language_code,is_bot,balance,trial\n" # Заголовки CSV + for user in users: + csv_data += f"{user['tg_id']},{user['username']},{user['first_name']},{user['last_name']},{user['language_code']},{user['is_bot']},{user['balance']},{user['trial']}\n" + + file_name = BytesIO(csv_data.encode("utf-8-sig")) + file_name.seek(0) + + file = BufferedInputFile(file_name.getvalue(), filename="users_export.csv") + + await callback_query.message.answer_document( + file, + caption="📥 Экспорт пользователей в CSV", + reply_markup=kb + ) + file_name.close() + + except Exception as e: + logger.error(f"Ошибка при экспорте пользователей в CSV: {e}") + await callback_query.message.answer( + text="❗ Произошла ошибка при экспорте пользователей.", + reply_markup=kb + ) + + +@router.callback_query( + AdminPanelCallback.filter(F.action == "stats_export_payments_csv"), + IsAdminFilter(), +) +async def export_payments_csv(callback_query: CallbackQuery, session: Any): + kb = build_admin_back_kb("stats") + + try: + payments = await session.fetch( + """ + SELECT + u.tg_id, + u.username, + u.first_name, + u.last_name, + p.amount, + p.payment_system, + p.status, + p.created_at + FROM users u + JOIN payments p ON u.tg_id = p.tg_id + """ + ) + + if not payments: + await callback_query.message.answer( + text="📭 Нет платежей для экспорта.", + reply_markup=kb + ) + return + + csv_data = "tg_id,username,first_name,last_name,amount,payment_system,status,created_at\n" # Заголовки CSV + for payment in payments: + csv_data += f"{payment['tg_id']},{payment['username']},{payment['first_name']},{payment['last_name']},{payment['amount']},{payment['payment_system']},{payment['status']},{payment['created_at']}\n" + + file_name = BytesIO(csv_data.encode("utf-8-sig")) + file_name.seek(0) + + file = BufferedInputFile(file_name.getvalue(), filename="payments_export.csv") + + await callback_query.message.answer_document( + document=file, + caption="📥 Экспорт платежей в CSV", + reply_markup=kb + ) + file_name.close() + + except Exception as e: + logger.error(f"Ошибка при экспорте платежей в CSV: {e}") + await callback_query.message.answer( + text="❗ Произошла ошибка при экспорте платежей.", + reply_markup=kb + ) diff --git a/keyboards/admin/stats_kb.py b/keyboards/admin/stats_kb.py new file mode 100644 index 00000000..3e3b9b04 --- /dev/null +++ b/keyboards/admin/stats_kb.py @@ -0,0 +1,24 @@ +from aiogram.types import InlineKeyboardMarkup +from aiogram.utils.keyboard import InlineKeyboardBuilder + +from keyboards.admin.panel_kb import build_admin_back_btn, AdminPanelCallback + + +def build_stats_kb() -> InlineKeyboardMarkup: + builder = InlineKeyboardBuilder() + builder.button( + text="🔄 Обновить", + callback_data=AdminPanelCallback(action="stats").pack() + ) + builder.button( + text="📥 Выгрузить пользователей в CSV", + callback_data=AdminPanelCallback(action="stats_export_users_csv").pack() + ) + builder.button( + text="📥 Выгрузить оплаты в CSV", + callback_data=AdminPanelCallback(action="stats_export_payments_csv").pack() + ) + builder.row( + build_admin_back_btn("admin") + ) + return builder.as_markup()