diff --git a/handlers/admin/admin_users.py b/handlers/admin/admin_users.py index cf97d751..41abeece 100644 --- a/handlers/admin/admin_users.py +++ b/handlers/admin/admin_users.py @@ -6,10 +6,10 @@ from aiogram import F, Router, types from aiogram.fsm.context import FSMContext from aiogram.fsm.state import State, StatesGroup from aiogram.types import CallbackQuery +from filters.admin import IsAdminFilter from config import TOTAL_GB from database import delete_user_data, get_client_id_by_email, get_servers_from_db, restore_trial, update_key_expiry -from filters.admin import IsAdminFilter from handlers.keys.key_utils import ( delete_key_from_cluster, delete_key_from_db, @@ -18,7 +18,8 @@ from handlers.keys.key_utils import ( from handlers.utils import sanitize_key_name from keyboards.admin.panel_kb import AdminPanelCallback, build_admin_back_kb from keyboards.admin.users_kb import build_user_edit_kb, build_key_edit_kb, build_key_delete_kb, \ - build_user_delete_kb, AdminUserEditorCallback, build_editor_kb + build_user_delete_kb, AdminUserEditorCallback, build_editor_kb, build_users_balance_kb, \ + build_users_balance_change_kb from logger import logger router = Router() @@ -29,7 +30,7 @@ class UserEditorState(StatesGroup): waiting_for_user_data = State() waiting_for_key_name = State() # updating data - waiting_for_new_balance = State() + waiting_for_balance = State() waiting_for_expiry_time = State() waiting_for_message_text = State() @@ -210,54 +211,156 @@ async def handle_trial_restore( @router.callback_query( - AdminUserEditorCallback.filter(F.action == "users_balance_change"), + AdminUserEditorCallback.filter(F.action == "users_balance_edit"), IsAdminFilter() ) async def handle_balance_change( + callback_query: CallbackQuery, + callback_data: AdminUserEditorCallback, + session: Any +): + tg_id = callback_data.tg_id + + records = await session.fetch(""" + SELECT amount, payment_system, status, created_at + FROM payments + WHERE tg_id = $1 + ORDER BY created_at DESC + LIMIT 5 + """, tg_id) + + balance = await get_user_balance(tg_id, session) + + text = ( + f"💵 Изменение баланса" + f"\n\n🆔 ID: {tg_id}" + f"\n💰 Баланс: {balance}Р" + f"\n📊 Последние операции:" + ) + + if records: + for record in records: + amount = record["amount"] + payment_system = record["payment_system"] + status = record["status"] + date = record["created_at"].strftime("%Y-%m-%d %H:%M:%S") + text += ( + f"\n\n
Сумма: {amount} | {payment_system}" + f"\nСтатус: {status}" + f"\nДата: {date}
" + ) + else: + text += "\n 🚫 Отсутствуют" + + await callback_query.message.edit_text( + text=text, + reply_markup=build_users_balance_kb(tg_id) + ) + + +@router.callback_query( + AdminUserEditorCallback.filter(F.action == "users_balance_add"), + IsAdminFilter() +) +async def handle_balance_add( + callback_query: CallbackQuery, + callback_data: AdminUserEditorCallback, + state: FSMContext, + session: Any +): + tg_id = callback_data.tg_id + amount = callback_data.data + + if amount: + await add_user_balance(tg_id, int(amount), session) + await handle_balance_change(callback_query, callback_data, session) + return + + await state.update_data(tg_id=tg_id, op_type="add") + await state.set_state(UserEditorState.waiting_for_balance) + + await callback_query.message.answer( + text="✍️ Введите сумму, которую хотите добавить на баланс пользователя:", + reply_markup=build_users_balance_change_kb(tg_id) + ) + + +@router.callback_query( + AdminUserEditorCallback.filter(F.action == "users_balance_take"), + IsAdminFilter() +) +async def handle_balance_add( callback_query: CallbackQuery, callback_data: AdminUserEditorCallback, state: FSMContext ): - await state.update_data(tg_id=callback_data.tg_id) - await callback_query.message.edit_text( - text="💸 Введите новую сумму баланса:", - reply_markup=build_editor_kb(callback_data.tg_id) + tg_id = callback_data.tg_id + + await state.update_data(tg_id=tg_id, op_type="take") + await state.set_state(UserEditorState.waiting_for_balance) + + await callback_query.message.answer( + text="✍️ Введите сумму, которую хотите добавить на баланс пользователя:", + reply_markup=build_users_balance_change_kb(tg_id) + ) + + +@router.callback_query( + AdminUserEditorCallback.filter(F.action == "users_balance_set"), + IsAdminFilter() +) +async def handle_balance_add( + callback_query: CallbackQuery, + callback_data: AdminUserEditorCallback, + state: FSMContext +): + tg_id = callback_data.tg_id + + await state.update_data(tg_id=tg_id, op_type="set") + await state.set_state(UserEditorState.waiting_for_balance) + + await callback_query.message.answer( + text="✍️ Введите баланс, который хотите установить пользователю:", + reply_markup=build_users_balance_change_kb(tg_id) ) - await state.set_state(UserEditorState.waiting_for_new_balance) @router.message( - UserEditorState.waiting_for_new_balance, + UserEditorState.waiting_for_balance, IsAdminFilter() ) -async def handle_new_balance_input( +async def handle_balance_input( message: types.Message, state: FSMContext, session: Any ): - user_data = await state.get_data() - tg_id = user_data.get("tg_id") + data = await state.get_data() + tg_id = data.get("tg_id") + op_type = data.get("op_type") if not message.text.isdigit() or int(message.text) < 0: await message.answer( - text="❌ Пожалуйста, введите корректную сумму для изменения баланса.", - reply_markup=build_editor_kb(tg_id), + text="🚫 Пожалуйста, введите корректную сумму!", + reply_markup=build_users_balance_change_kb(tg_id) ) return - new_balance = int(message.text) + amount = int(message.text) - await session.execute( - "UPDATE connections SET balance = $1 WHERE tg_id = $2", - new_balance, - tg_id, - ) + if op_type == "add": + text = f"✅ К балансу пользователя добавлено {amount}Р" + await add_user_balance(tg_id, amount, session) + elif op_type == "take": + text = f"✅ Из баланса пользователя было вычтено {amount}Р" + await add_user_balance(tg_id, -amount, session) + else: + text = f"✅ Баланс пользователя изменен на {amount}Р" + await set_user_balance(tg_id, amount, session) await message.answer( - text=f"✅ Баланс успешно изменен на {new_balance}", - reply_markup=build_admin_back_kb() + text=text, + reply_markup=build_users_balance_change_kb(tg_id) ) - await state.clear() @router.callback_query( @@ -280,10 +383,11 @@ async def handle_key_edit( return text = ( - f"🔑 Ключ: {key_details['key']}\n" - f"⏰ Дата истечения: {key_details['expiry_date']}\n" - f"💰 Баланс пользователя: {key_details['balance']}\n" - f"🌐 Кластер: {key_details['server_name']}" + f"🔑 Информация о ключе" + f"\n\n{key_details['key']}" + f"\n\n⏰ Дата истечения: {key_details['expiry_date']}" + f"\n🌐 Кластер: {key_details['server_name']}" + f"\n🆔 ID клиента: {key_details['tg_id']}" ) await callback_query.message.edit_text( @@ -303,7 +407,7 @@ async def handle_change_expiry( ): email = callback_data.data await callback_query.message.edit_text( - text=f"⏳ Введите новое время истечения для ключа {email} в формате YYYY-MM-DD HH:MM:SS:" + text=f"✍️ Введите новое время истечения для ключа {email} в формате YYYY-MM-DD HH:MM:SS:" ) await state.update_data(tg_id=callback_data.tg_id, email=email) await state.set_state(UserEditorState.waiting_for_expiry_time) @@ -523,7 +627,7 @@ async def handle_editor( state, session, callback_data.tg_id, - callback_data.data == "edit" + callback_data.edit ) @@ -536,9 +640,6 @@ async def process_user_search( ) -> None: await state.clear() - username = await session.fetchval( - "SELECT username FROM users WHERE tg_id = $1", tg_id - ) balance = await session.fetchval( "SELECT balance FROM connections WHERE tg_id = $1", tg_id ) @@ -550,29 +651,35 @@ async def process_user_search( ) return - key_records = await session.fetch("SELECT email FROM keys WHERE tg_id = $1", tg_id) + username = await session.fetchval( + "SELECT username FROM users WHERE tg_id = $1", tg_id + ) + key_records = await session.fetch( + "SELECT email FROM keys WHERE tg_id = $1", tg_id + ) referral_count = await session.fetchval( "SELECT COUNT(*) FROM referrals WHERE referrer_tg_id = $1", tg_id ) text = ( - f"📊 Информация о пользователе:\n\n" - f"🆔 ID пользователя: {tg_id}\n" - f"👤 Логин пользователя: @{username}\n" - f"💰 Баланс: {balance}\n" - f"👥 Количество рефералов: {referral_count}\n" - f"🔑 Ключи (для редактирования нажмите на ключ):" + f"📊 Информация о пользователе" + f"\n\n🆔 ID: {tg_id}" + f"\n📄 Логин: @{username}" + f"\n💰 Баланс: {balance}" + f"\n👥 Количество рефералов: {referral_count}" ) + kb = build_user_edit_kb(tg_id, key_records) + if edit: await message.edit_text( text=text, - reply_markup=build_user_edit_kb(tg_id, key_records) + reply_markup=kb ) else: await message.answer( text=text, - reply_markup=build_user_edit_kb(tg_id, key_records) + reply_markup=kb ) @@ -620,3 +727,33 @@ async def get_key_details(email, session): "balance": record["balance"], "tg_id": record["tg_id"], } + + +async def get_user_balance(tg_id: int, session: Any) -> float: + try: + return await session.fetchval( + "SELECT balance FROM connections WHERE tg_id = $1", tg_id, + ) + except Exception as e: + logger.error(f"Ошибка при получении баланса для пользователя {tg_id}: {e}") + return -1 + + +async def add_user_balance(tg_id: int, balance: int, session: Any) -> None: + try: + await session.execute( + "UPDATE connections SET balance = balance + $1 WHERE tg_id = $2", + balance, tg_id, + ) + except Exception as e: + logger.error(f"Ошибка при добавлении баланса для пользователя {tg_id}: {e}") + + +async def set_user_balance(tg_id: int, balance: int, session: Any) -> None: + try: + await session.execute( + "UPDATE connections SET balance = $1 WHERE tg_id = $2", + balance, tg_id, + ) + except Exception as e: + logger.error(f"Ошибка при установке баланса для пользователя {tg_id}: {e}") diff --git a/keyboards/admin/users_kb.py b/keyboards/admin/users_kb.py index 66f958f6..8d01bde4 100644 --- a/keyboards/admin/users_kb.py +++ b/keyboards/admin/users_kb.py @@ -2,13 +2,15 @@ from aiogram.filters.callback_data import CallbackData from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup from aiogram.utils.keyboard import InlineKeyboardBuilder +from config import RENEWAL_PRICES from keyboards.admin.panel_kb import build_admin_back_btn class AdminUserEditorCallback(CallbackData, prefix="admin_users"): action: str tg_id: int - data: str | None = None + data: str | int | None = None + edit: bool = False def build_user_edit_kb(tg_id: int, key_records: list) -> InlineKeyboardMarkup: @@ -28,7 +30,7 @@ def build_user_edit_kb(tg_id: int, key_records: list) -> InlineKeyboardMarkup: builder.button( text="💸 Изменить баланс", callback_data=AdminUserEditorCallback( - action="users_balance_change", + action="users_balance_edit", tg_id=tg_id ).pack() ) @@ -55,7 +57,7 @@ def build_user_edit_kb(tg_id: int, key_records: list) -> InlineKeyboardMarkup: ).pack() ) builder.row( - build_editor_btn("🔄 Обновить", tg_id, edit=True) + build_editor_btn("🔄 Обновить данные", tg_id, edit=True) ) builder.row( build_admin_back_btn() @@ -64,6 +66,65 @@ def build_user_edit_kb(tg_id: int, key_records: list) -> InlineKeyboardMarkup: return builder.as_markup() +def build_users_balance_change_kb(tg_id: int) -> InlineKeyboardMarkup: + builder = InlineKeyboardBuilder() + builder.button( + text="🔙 Назад", # todo: fix magic text was set + callback_data=AdminUserEditorCallback( + action="users_balance_edit", + tg_id=tg_id + ).pack() + ) + return builder.as_markup() + + +def build_users_balance_kb(tg_id: int) -> InlineKeyboardMarkup: + builder = InlineKeyboardBuilder() + for month, amount in RENEWAL_PRICES.items(): + builder.button( + text=f"+ {amount}Р ({month} мес.)", + callback_data=AdminUserEditorCallback( + action="users_balance_add", + tg_id=tg_id, + data=amount + ).pack() + ) + builder.button( + text=f"- {amount}Р ({month} мес.)", + callback_data=AdminUserEditorCallback( + action="users_balance_add", + tg_id=tg_id, + data=-amount + ).pack() + ) + builder.button( + text="💵 Добавить", + callback_data=AdminUserEditorCallback( + action="users_balance_add", + tg_id=tg_id + ).pack() + ) + builder.button( + text="💵 Вычесть", + callback_data=AdminUserEditorCallback( + action="users_balance_take", + tg_id=tg_id + ).pack() + ) + builder.button( + text="💵 Установить баланс", + callback_data=AdminUserEditorCallback( + action="users_balance_set", + tg_id=tg_id + ).pack() + ) + builder.row( + build_editor_back_btn(tg_id, True) + ) + builder.adjust(2, 2, 2, 2, 2, 1) + return builder.as_markup() + + def build_user_delete_kb(tg_id: int): builder = InlineKeyboardBuilder() builder.button( @@ -145,7 +206,7 @@ def build_editor_btn(text: str, tg_id: int, edit: bool = False) -> InlineKeyboar text=text, callback_data=AdminUserEditorCallback( action="users_editor", - data="" if edit else "edit", - tg_id=tg_id + tg_id=tg_id, + edit=edit ).pack() )