diff --git a/database.py b/database.py index 0472eac5..2a293fd0 100644 --- a/database.py +++ b/database.py @@ -1196,3 +1196,12 @@ async def get_servers_from_db(): ) return servers + + +async def delete_user_data(session: Any, tg_id: int): + await session.execute("DELETE FROM gifts WHERE sender_tg_id = $1 OR recipient_tg_id = $1", tg_id) + await session.execute("DELETE FROM payments WHERE tg_id = $1", tg_id) + await session.execute("DELETE FROM users WHERE tg_id = $1", tg_id) + await session.execute("DELETE FROM connections WHERE tg_id = $1", tg_id) + await session.execute("DELETE FROM keys WHERE tg_id = $1", tg_id) + await session.execute("DELETE FROM referrals WHERE referrer_tg_id = $1", tg_id) diff --git a/handlers/admin/admin_user_editor.py b/handlers/admin/admin_user_editor.py index 8a89d875..7d52d3ac 100644 --- a/handlers/admin/admin_user_editor.py +++ b/handlers/admin/admin_user_editor.py @@ -5,7 +5,7 @@ from typing import Any from aiogram import F, Router, types from aiogram.fsm.context import FSMContext from aiogram.fsm.state import State, StatesGroup -from aiogram.types import CallbackQuery, InlineKeyboardButton +from aiogram.types import CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup from aiogram.utils.keyboard import InlineKeyboardBuilder from config import TOTAL_GB @@ -15,6 +15,7 @@ from database import ( restore_trial, update_key_expiry, ) + from filters.admin import IsAdminFilter from handlers.keys.key_utils import ( delete_key_from_cluster, @@ -117,7 +118,7 @@ async def handle_username_input( callback_data=f"restore_trial_{tg_id}", ) ) - + builder.row(InlineKeyboardButton(text="❌ Удалить клиента", callback_data=f"confirm_delete_user_{tg_id}")) builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor")) user_info = ( @@ -606,20 +607,9 @@ async def handle_user_info( InlineKeyboardButton(text=f"🔑 {email}", callback_data=f"edit_key_{email}") ) - builder.row( - InlineKeyboardButton( - text="📝 Изменить баланс", - callback_data=f"change_balance_{tg_id}", - ) - ) - - builder.row( - InlineKeyboardButton( - text="🔄 Восстановить пробник", - callback_data=f"restore_trial_{tg_id}", - ) - ) - + builder.row(InlineKeyboardButton(text="📝 Изменить баланс", callback_data=f"change_balance_{tg_id}")) + builder.row(InlineKeyboardButton(text="🔄 Восстановить пробник", callback_data=f"restore_trial_{tg_id}")) + builder.row(InlineKeyboardButton(text="❌ Удалить клиента", callback_data=f"confirm_delete_user_{tg_id}")) builder.row(InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor")) user_info = ( @@ -630,5 +620,56 @@ async def handle_user_info( f"👥 Количество рефералов: {referral_count}\n" f"🔑 Ключи (для редактирования нажмите на ключ):" ) + await callback_query.message.answer(user_info, reply_markup=builder.as_markup()) await state.set_state(UserEditorState.displaying_user_info) + + +@router.callback_query(F.data.startswith("confirm_delete_user_"), IsAdminFilter()) +async def confirm_delete_user(callback_query: types.CallbackQuery, state: FSMContext, session: Any): + tg_id = int(callback_query.data.split("_")[3]) + + confirmation_markup = InlineKeyboardMarkup( + row_width=2, + inline_keyboard=[ + [InlineKeyboardButton(text="✅ Подтвердить", callback_data=f"delete_user_{tg_id}")], + [InlineKeyboardButton(text="❌ Отменить", callback_data="user_editor")], + ], + ) + + await callback_query.message.answer( + f"Вы уверены, что хотите удалить пользователя с ID {tg_id}?", reply_markup=confirmation_markup + ) + + +@router.callback_query(F.data.startswith("delete_user_"), IsAdminFilter()) +async def delete_user(callback_query: types.CallbackQuery, session: Any): + tg_id = int(callback_query.data.split("_")[2]) + + key_records = await session.fetch("SELECT email, client_id FROM keys WHERE tg_id = $1", tg_id) + + async def delete_keys_from_servers(): + try: + tasks = [] + for email, client_id in key_records: + servers = await get_servers_from_db() + for cluster_id, cluster in servers.items(): + tasks.append(delete_key_from_cluster(cluster_id, email, client_id)) + await asyncio.gather(*tasks) + except Exception as e: + logger.error(f"Ошибка при удалении ключей с серверов для пользователя {tg_id}: {e}") + + await delete_keys_from_servers() + + try: + await delete_user_data(session, tg_id) + + back_button = InlineKeyboardButton(text="🔙 Назад", callback_data="user_editor") + keyboard = InlineKeyboardMarkup(inline_keyboard=[[back_button]]) + + await callback_query.message.answer(f"🗑️ Пользователь с ID {tg_id} был удален.", reply_markup=keyboard) + except Exception as e: + logger.error(f"Ошибка при удалении данных из базы данных для пользователя {tg_id}: {e}") + await callback_query.message.answer( + f"❌ Произошла ошибка при удалении пользователя с ID {tg_id}. Попробуйте снова." + ) diff --git a/handlers/keys/subscriptions.py b/handlers/keys/subscriptions.py index 0868d84f..4073dc71 100644 --- a/handlers/keys/subscriptions.py +++ b/handlers/keys/subscriptions.py @@ -8,6 +8,9 @@ from config import DATABASE_URL, TRANSITION_DATE_STR from database import get_servers_from_db from logger import logger +import urllib.parse + +from config import PROJECT_NAME, NEWS_MESSAGE async def fetch_url_content(url, tg_id): @@ -75,10 +78,12 @@ async def handle_old_subscription(request): conn = await asyncpg.connect(DATABASE_URL) try: + key_info = await conn.fetchrow( "SELECT created_at FROM keys WHERE email = $1", email ) + if not key_info: logger.warning(f"Клиент с email {email} не найден в базе.") return web.Response( @@ -87,7 +92,8 @@ async def handle_old_subscription(request): ) created_at_ms = key_info["created_at"] - logger.info(f"Значение created_at для клиента с email {email}: {created_at_ms}") + cluster_name = key_info["cluster_name"] + logger.info(f"Значение created_at для клиента с email {email}: {created_at_ms}, кластер: {cluster_name}") created_at_datetime = datetime.utcfromtimestamp(created_at_ms / 1000) logger.info( @@ -102,12 +108,13 @@ async def handle_old_subscription(request): ) servers = await get_servers_from_db() + cluster_servers = servers.get(cluster_name, []) + logger.info(f"Сервера в кластере: {cluster_servers}") urls = [] - for cluster_name, cluster_servers in servers.items(): - for server in cluster_servers: - server_subscription_url = f"{server['subscription_url']}/{email}" - urls.append(server_subscription_url) + for server in cluster_servers: + server_subscription_url = f"{server['subscription_url']}/{email}" + urls.append(server_subscription_url) combined_subscriptions = await combine_unique_lines(urls, email, "") @@ -115,11 +122,12 @@ async def handle_old_subscription(request): "\n".join(combined_subscriptions).encode("utf-8") ).decode("utf-8") + encoded_project_name = urllib.parse.quote(f"{PROJECT_NAME}\n{NEWS_MESSAGE}") headers = { "Content-Type": "text/plain; charset=utf-8", "Content-Disposition": "inline", "profile-update-interval": "7", - "profile-title": email, + "profile-title": encoded_project_name, } logger.info(f"Возвращаем объединенные подписки для email: {email}") @@ -144,10 +152,12 @@ async def handle_new_subscription(request): conn = await asyncpg.connect(DATABASE_URL) try: + client_data = await conn.fetchrow( "SELECT tg_id FROM keys WHERE email = $1", email ) + if not client_data: logger.warning(f"Клиент с email {email} не найден в базе.") return web.Response( @@ -156,6 +166,7 @@ async def handle_new_subscription(request): ) stored_tg_id = client_data["tg_id"] + cluster_name = client_data["server_id"] if str(tg_id) != str(stored_tg_id): logger.warning(f"Неверный tg_id для клиента с email {email}.") @@ -163,16 +174,17 @@ async def handle_new_subscription(request): text="❌ Неверные данные. Получите свой ключ в боте.", status=403, ) + finally: await conn.close() servers = await get_servers_from_db() + cluster_servers = servers.get(cluster_name, []) urls = [] - for cluster_name, cluster_servers in servers.items(): - for server in cluster_servers: - server_subscription_url = f"{server['subscription_url']}/{email}" - urls.append(server_subscription_url) + for server in cluster_servers: + server_subscription_url = f"{server['subscription_url']}/{email}" + urls.append(server_subscription_url) query_string = request.query_string logger.info(f"Извлечен query string: {query_string}") @@ -183,11 +195,12 @@ async def handle_new_subscription(request): "\n".join(combined_subscriptions).encode("utf-8") ).decode("utf-8") + encoded_project_name = urllib.parse.quote(f"{PROJECT_NAME}\n{NEWS_MESSAGE}") headers = { "Content-Type": "text/plain; charset=utf-8", "Content-Disposition": "inline", "profile-update-interval": "7", - "profile-title": email, + "profile-title": encoded_project_name, } logger.info(f"Возвращаем объединенные подписки для email: {email}") diff --git a/handlers/payments/yookassa_pay.py b/handlers/payments/yookassa_pay.py index 62760122..752f2910 100644 --- a/handlers/payments/yookassa_pay.py +++ b/handlers/payments/yookassa_pay.py @@ -132,7 +132,7 @@ async def process_amount_selection( "description": "Пополнение баланса", "quantity": "1.00", "amount": {"value": str(amount), "currency": "RUB"}, - "vat_code": 6, + "vat_code": 1, } ], }, @@ -232,7 +232,7 @@ async def process_custom_amount_input(message: types.Message, state: FSMContext) "value": str(amount), "currency": "RUB", }, - "vat_code": 6, + "vat_code": 1, } ], },