Add users rating feature

This commit is contained in:
3252a8
2026-02-09 10:03:55 +03:00
parent 049789c9c5
commit 5247092ca2
6 changed files with 239 additions and 3 deletions
+3
View File
@@ -141,6 +141,9 @@ async def admin_panel_actions_callback_handler(
from . import payments as admin_payments_handlers
await admin_payments_handlers.view_payments_handler(
callback, i18n_data, settings, session)
elif action == "user_ratings":
await admin_stats_handlers.show_user_ratings_handler(
callback, i18n_data, settings, session)
elif action == "ads":
from . import ads as admin_ads_handlers
await admin_ads_handlers.show_ads_menu(callback, settings, i18n_data, session)
+99 -1
View File
@@ -2,6 +2,7 @@ import logging
from aiogram import Router, F, types
from typing import Optional, Dict, List
from datetime import datetime
import html
from sqlalchemy.ext.asyncio import AsyncSession
from config.settings import Settings
@@ -10,12 +11,34 @@ from db.dal import user_dal, payment_dal, panel_sync_dal
from db.models import Payment, PanelSyncStatus
from bot.services.panel_api_service import PanelApiService
from bot.keyboards.inline.admin_keyboards import get_back_to_admin_panel_keyboard
from bot.keyboards.inline.admin_keyboards import (
get_back_to_admin_panel_keyboard,
get_back_to_user_management_keyboard,
)
from bot.middlewares.i18n import JsonI18n
router = Router(name="admin_statistics_router")
def _format_rating_user_label(user_row: Dict[str, object]) -> str:
user_id = int(user_row.get("user_id", 0) or 0)
username = user_row.get("username")
first_name = user_row.get("first_name")
parts: List[str] = []
if username:
parts.append(f"@{html.escape(str(username))}")
elif first_name:
parts.append(html.escape(str(first_name)))
if not parts:
parts.append(f"ID {user_id}")
else:
parts.append(f"(ID {user_id})")
return " ".join(parts)
async def show_statistics_handler(callback: types.CallbackQuery,
i18n_data: dict, settings: Settings,
session: AsyncSession):
@@ -255,3 +278,78 @@ async def show_statistics_handler(callback: types.CallbackQuery,
reply_markup=get_back_to_admin_panel_keyboard(
current_lang, i18n))
break
async def show_user_ratings_handler(
callback: types.CallbackQuery,
i18n_data: dict,
settings: Settings,
session: AsyncSession,
):
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 displaying ratings.", show_alert=True)
return
_ = lambda key, **kwargs: i18n.gettext(current_lang, key, **kwargs)
await callback.answer()
top_limit = 10
traffic_top = await user_dal.get_top_users_by_traffic_used(session, limit=top_limit)
invited_top = await user_dal.get_top_users_by_referrals_count(session, limit=top_limit)
revenue_top = await user_dal.get_top_users_by_referral_revenue(session, limit=top_limit)
text_parts: List[str] = [
_("admin_user_ratings_header", top_limit=top_limit),
"",
f"<b>{_('admin_user_ratings_traffic_title')}</b>",
]
if traffic_top:
for idx, row in enumerate(traffic_top, start=1):
traffic_gb = float(row.get("traffic_used_bytes") or 0) / (1024**3)
text_parts.append(
_(
"admin_user_ratings_traffic_item",
rank=idx,
user=_format_rating_user_label(row),
traffic_gb=f"{traffic_gb:.2f}",
)
)
else:
text_parts.append(_("admin_user_ratings_empty"))
text_parts.extend(["", f"<b>{_('admin_user_ratings_invited_title')}</b>"])
if invited_top:
for idx, row in enumerate(invited_top, start=1):
text_parts.append(
_(
"admin_user_ratings_invited_item",
rank=idx,
user=_format_rating_user_label(row),
invited_count=int(row.get("invited_count") or 0),
)
)
else:
text_parts.append(_("admin_user_ratings_empty"))
text_parts.extend(["", f"<b>{_('admin_user_ratings_revenue_title')}</b>"])
if revenue_top:
for idx, row in enumerate(revenue_top, start=1):
text_parts.append(
_(
"admin_user_ratings_revenue_item",
rank=idx,
user=_format_rating_user_label(row),
revenue=f"{float(row.get('referral_revenue') or 0):.2f}",
)
)
else:
text_parts.append(_("admin_user_ratings_empty"))
await callback.message.edit_text(
"\n".join(text_parts),
reply_markup=get_back_to_user_management_keyboard(current_lang, i18n),
parse_mode="HTML",
)
+21 -1
View File
@@ -64,10 +64,12 @@ def get_user_management_keyboard(i18n_instance, lang: str) -> InlineKeyboardMark
callback_data="admin_action:users_search_prompt")
builder.button(text=_(key="admin_ban_management_section"),
callback_data="admin_section:ban_management")
builder.button(text=_(key="admin_user_ratings_button"),
callback_data="admin_action:user_ratings")
builder.button(text=_(key="back_to_admin_panel_button"),
callback_data="admin_action:main")
builder.adjust(2, 1, 1)
builder.adjust(2, 2, 1)
return builder.as_markup()
@@ -460,3 +462,21 @@ def get_back_to_admin_panel_keyboard(lang: str,
builder.button(text=_(key="back_to_admin_panel_button"),
callback_data="admin_action:main")
return builder.as_markup()
def get_back_to_stats_monitoring_keyboard(lang: str,
i18n_instance) -> InlineKeyboardMarkup:
_ = lambda key, **kwargs: i18n_instance.gettext(lang, key, **kwargs)
builder = InlineKeyboardBuilder()
builder.button(text=_(key="back_to_stats_monitoring_button"),
callback_data="admin_section:stats_monitoring")
return builder.as_markup()
def get_back_to_user_management_keyboard(lang: str,
i18n_instance) -> InlineKeyboardMarkup:
_ = lambda key, **kwargs: i18n_instance.gettext(lang, key, **kwargs)
builder = InlineKeyboardBuilder()
builder.button(text=_(key="back_to_user_management_button"),
callback_data="admin_section:user_management")
return builder.as_markup()