From 217bb457a1a1d49d28f9dec7cea4e3d7142fb7ba Mon Sep 17 00:00:00 2001 From: Vladless Date: Fri, 13 Dec 2024 23:01:58 +0300 Subject: [PATCH] sub_name/delete user --- database.py | 21 +++++--- handlers/__init__.py | 1 - handlers/admin/admin_user_editor.py | 74 ++++++++++++++++++++++------- handlers/keys/subscriptions.py | 35 +++++++++----- handlers/profile.py | 20 ++++---- 5 files changed, 102 insertions(+), 49 deletions(-) diff --git a/database.py b/database.py index a51ceb17..ad91821f 100644 --- a/database.py +++ b/database.py @@ -915,12 +915,12 @@ async def get_tg_id_by_client_id(client_id: str): async def upsert_user( - tg_id: int, - username: str = None, - first_name: str = None, - last_name: str = None, - language_code: str = None, - is_bot: bool = False, + tg_id: int, + username: str = None, + first_name: str = None, + last_name: str = None, + language_code: str = None, + is_bot: bool = False, ): """ Обновляет или вставляет информацию о пользователе в базу данных. @@ -1117,3 +1117,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/__init__.py b/handlers/__init__.py index 613d08f9..5037c650 100644 --- a/handlers/__init__.py +++ b/handlers/__init__.py @@ -22,7 +22,6 @@ router.include_routers( donate_router, coupons_router, notifications_router, - payments_router, keys_router, instructions_router, diff --git a/handlers/admin/admin_user_editor.py b/handlers/admin/admin_user_editor.py index d579cb5f..7a9bd856 100644 --- a/handlers/admin/admin_user_editor.py +++ b/handlers/admin/admin_user_editor.py @@ -5,11 +5,11 @@ 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 -from database import get_client_id_by_email, get_servers_from_db, restore_trial, update_key_expiry +from database import get_client_id_by_email, get_servers_from_db, restore_trial, update_key_expiry, delete_user_data from filters.admin import IsAdminFilter from handlers.keys.key_utils import delete_key_from_cluster, delete_key_from_db, renew_key_in_cluster from handlers.utils import sanitize_key_name @@ -86,7 +86,7 @@ async def handle_username_input(message: types.Message, state: FSMContext, sessi 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 = ( @@ -499,20 +499,9 @@ async def handle_user_info(callback_query: types.CallbackQuery, state: FSMContex for (email,) in key_records: builder.row(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 = ( @@ -523,5 +512,56 @@ async def handle_user_info(callback_query: types.CallbackQuery, state: FSMContex 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 45b0d3fc..0f735199 100644 --- a/handlers/keys/subscriptions.py +++ b/handlers/keys/subscriptions.py @@ -8,6 +8,9 @@ import asyncpg 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): @@ -67,7 +70,7 @@ 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) + key_info = await conn.fetchrow("SELECT created_at, cluster_name FROM keys WHERE email = $1", email) if not key_info: logger.warning(f"Клиент с email {email} не найден в базе.") @@ -77,7 +80,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(f"Время создания клиента в формате datetime (UTC): {created_at_datetime}") @@ -90,22 +94,24 @@ 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, "") base64_encoded = base64.b64encode("\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}") @@ -130,7 +136,7 @@ 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) + client_data = await conn.fetchrow("SELECT tg_id, server_id FROM keys WHERE email = $1", email) if not client_data: logger.warning(f"Клиент с email {email} не найден в базе.") @@ -140,6 +146,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}.") @@ -147,16 +154,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}") @@ -165,11 +173,12 @@ async def handle_new_subscription(request): base64_encoded = base64.b64encode("\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/profile.py b/handlers/profile.py index 80775e0e..3a43d47f 100644 --- a/handlers/profile.py +++ b/handlers/profile.py @@ -68,18 +68,15 @@ async def view_tariffs_handler(callback_query: types.CallbackQuery): builder = InlineKeyboardBuilder() builder.row(InlineKeyboardButton(text="👤 Личный кабинет", callback_data="profile")) - image_path = os.path.join("img", "tariffs.jpg") + image_path = os.path.join("img", "tariffs.jpg") - tariffs_message = ( - "🚀 Доступные тарифы VPN:\n\n" - + "\n".join( - [ - f"{months} {'месяц' if months == '1' else 'месяца' if int(months) in [2, 3, 4] else 'месяцев'}: " - f"{RENEWAL_PLANS[months]['price']} " - f"{'💳' if months == '1' else '🌟' if months == '3' else '🔥' if months == '6' else '🚀'} рублей" - for months in sorted(RENEWAL_PLANS.keys(), key=int) - ] - ) + tariffs_message = "🚀 Доступные тарифы VPN:\n\n" + "\n".join( + [ + f"{months} {'месяц' if months == '1' else 'месяца' if int(months) in [2, 3, 4] else 'месяцев'}: " + f"{RENEWAL_PLANS[months]['price']} " + f"{'💳' if months == '1' else '🌟' if months == '3' else '🔥' if months == '6' else '🚀'} рублей" + for months in sorted(RENEWAL_PLANS.keys(), key=int) + ] ) if os.path.isfile(image_path): @@ -96,7 +93,6 @@ async def view_tariffs_handler(callback_query: types.CallbackQuery): ) - @router.callback_query(F.data == "invite") async def invite_handler(callback_query: types.CallbackQuery): chat_id = callback_query.message.chat.id