Встроил марзбан в бекенд, исправил бывшие проблемы с получением активной подписки

This commit is contained in:
root
2025-11-26 18:04:22 +03:00
parent e975bf4774
commit a001694608
6 changed files with 271 additions and 209 deletions

View File

@@ -1,11 +1,11 @@
from decimal import Decimal
import json
from instance.model import User, Subscription, Transaction
from app.services.marzban import MarzbanService
from app.services.marzban import MarzbanService, MarzbanUser
from .postgres_rep import PostgresRepository
from instance.model import Transaction,TransactionType
from instance.model import Transaction,TransactionType, Plan
from dateutil.relativedelta import relativedelta
from datetime import datetime
from datetime import datetime, timezone
import random
import string
from typing import Optional
@@ -14,12 +14,13 @@ from uuid import UUID
class DatabaseManager:
def __init__(self, session_generator):
def __init__(self, session_generator,marzban_username,marzban_password,marzban_url):
"""
Инициализация с асинхронным генератором сессий (например, get_postgres_session).
"""
self.logger = logging.getLogger(__name__)
self.postgres_repo = PostgresRepository(session_generator, self.logger)
self.marzban_service = MarzbanService(marzban_url,marzban_username,marzban_password)
async def create_user(self, telegram_id: int, invented_by: Optional[int]= None):
"""
@@ -82,98 +83,131 @@ class DatabaseManager:
"""
Проверяет наличие активной подписки.
"""
return await self.postgres_repo.get_active_subscription(telegram_id)
try:
return await self.postgres_repo.get_active_subscription(telegram_id)
except Exception as e:
self.logger.error(f"Неожиданная ошибка в get_active_subscription: {str(e)}")
return "ERROR"
async def get_plan_by_id(self, plan_id):
"""
Ищет по названию плана.
"""
try:
return await self.postgres_repo.get_plan_by_id(plan_id)
except Exception as e:
self.logger.error(f"Неожиданная ошибка в get_plan_by_name: {str(e)}")
return None
async def get_last_subscriptions(self, telegram_id: int, limit: int = 1):
"""
Возвращает список последних подписок.
"""
return await self.postgres_repo.get_last_subscription_by_user_id(telegram_id, limit)
return await self.postgres_repo.get_last_subscription_by_user_id(telegram_id)
async def buy_sub(self, telegram_id: int, plan_id: str):
async def buy_sub(self, telegram_id: int, plan_name: str):
"""
Покупает подписку.
"""
# active_subscription = await self.get_active_subscription(telegram_id)
# self.logger.info(f"{active_subscription}")
# if active_subscription:
# self.logger.error(f"Пользователь {telegram_id} уже имеет активную подписку.")
# return "ACTIVE_SUBSCRIPTION_EXISTS"
try:
self.logger.info(f"Начало покупки подписки для пользователя {telegram_id}, план: {plan_name}")
active_subscription = await self.get_active_subscription(telegram_id)
self.logger.info(f"Активная подписка: {active_subscription}")
# result = await self._initialize_user_and_plan(telegram_id, plan_id)
# if isinstance(result, str):
# return result # Возвращает "ERROR", "TARIFF_NOT_FOUND" или "INSUFFICIENT_FUNDS"
if active_subscription:
self.logger.error(f"Пользователь {telegram_id} уже имеет активную подписку.")
return "ACTIVE_SUBSCRIPTION_EXISTS"
# user, plan = result
# await self.postgres_repo.update_balance(user,-plan['price'])
# new_subscription, server = await self._create_subscription_and_add_client(user, plan)
result = await self._initialize_user_and_plan(telegram_id, plan_name)
if isinstance(result, str):
return result # Возвращает "ERROR", "TARIFF_NOT_FOUND" или "INSUFFICIENT_FUNDS"
# if not new_subscription:
# return "ERROR"
user, plan = result
self.logger.info(f"Пользователь и план найдены: user_id={user.telegram_id}, plan_price={plan.price}")
new_subscription = await self._create_subscription_and_add_client(user, plan)
if not new_subscription:
self.logger.error(f"Не удалось создать подписку для пользователя {telegram_id}")
return "ERROR"
# self.logger.info(f"Подписка успешно оформлена для пользователя {telegram_id}.")
# return "OK"
pass
updated = await self.postgres_repo.update_balance(user,-plan.price)
if updated == False:
self.logger.error(f"Не удалось обновить баланс для пользователя {telegram_id}")
return "ERROR"
async def _initialize_user_and_plan(self, telegram_id, plan_id):
self.logger.info(f"Подписка успешно оформлена для пользователя {telegram_id}.")
return {"status": "OK", "subscription_id": str(new_subscription.id)}
except Exception as e:
self.logger.error(f"Неожиданная ошибка в buy_sub: {str(e)}")
return "ERROR"
async def _initialize_user_and_plan(self, telegram_id, plan_name):
"""
Инициализирует пользователя и план подписки.
"""
# user = await self.get_user_by_telegram_id(telegram_id)
# if not user:
# self.logger.error(f"Пользователь с Telegram ID {telegram_id} не найден.")
# return "ERROR"
try:
# plan = await self.mongo_repo.get_subscription_plan(plan_id)
# if not plan:
# self.logger.error(f"Тарифный план {plan_id} не найден.")
# return "TARIFF_NOT_FOUND"
user = await self.get_user_by_telegram_id(telegram_id)
if not user:
self.logger.error(f"Пользователь с Telegram ID {telegram_id} не найден.")
return "ERROR"
# cost = int(plan["price"])
# if user.balance < cost:
# self.logger.error(f"Недостаточно средств у пользователя {telegram_id} для покупки плана {plan_id}.")
# return "INSUFFICIENT_FUNDS"
plan = await self.postgres_repo.get_subscription_plan(plan_name)
if not plan:
self.logger.error(f"Тарифный план {plan_name} не найден.")
return "TARIFF_NOT_FOUND"
# return user, plan
pass
cost = plan.price
if user.balance < cost:
self.logger.error(f"Недостаточно средств у пользователя {telegram_id} для покупки плана {plan_name}.")
return "INSUFFICIENT_FUNDS"
async def _create_subscription_and_add_client(self, user, plan):
"""
Создаёт подписку и добавляет клиента на сервер.
"""
# expiry_date = datetime.utcnow() + relativedelta(months=plan["duration_months"])
# server = await self.mongo_repo.get_server_with_least_clients()
# if not server:
# self.logger.error("Нет доступных серверов для подписки.")
# return None, None
return user, plan
except Exception as e:
self.logger.error(f"Неожиданная ошибка в _initialize_user_and_plan: {str(e)}")
return "ERROR"
# new_subscription = Subscription(
# user_id=user.id,
# vpn_server_id=str(server["server"]["name"]),
# plan=plan["name"],
# expiry_date=expiry_date,
# )
async def _create_subscription_and_add_client(self, user: User, plan: Plan):
"""Создаёт подписку и добавляет клиента на сервер."""
try:
self.logger.info(f"Создание подписки для user_id={user.telegram_id}, plan={plan.name}")
# Проверяем типы объектов
self.logger.info(f"Тип user: {type(user)}, тип plan: {type(plan)}")
expiry_date = datetime.utcnow() + relativedelta(days=plan.duration_days)
new_subscription = Subscription(
user_id=user.telegram_id,
vpn_server_id="BASE SERVER NEED TO UPDATE",
plan_id=plan.id,
end_date=expiry_date,
start_date=datetime.utcnow()
)
# panel = PanelInteraction(
# base_url=f"https://{server['server']['ip']}:{server['server']['port']}/{server['server']['secretKey']}",
# login_data={"username": server["server"]["login"], "password": server["server"]["password"]},
# logger=self.logger,
# certificate=server["server"]["certificate"]["data"],
# )
self.logger.info(f"Создан объект подписки: {new_subscription}")
response = await self.marzban_service.create_user(user, new_subscription)
self.logger.info(f"Ответ от Marzban: {response}")
# response = await panel.add_client(
# inbound_id=1,
# expiry_date=expiry_date.isoformat(),
# email=user.username,
# )
# if response != "OK":
# self.logger.error(f"Ошибка при добавлении клиента: {response}")
# return None, None
# await self.postgres_repo.add_record(new_subscription)
if response == "USER_ALREADY_EXISTS":
response = await self.marzban_service.get_user_status(user)
result = await self.marzban_service.update_user(user, new_subscription)
# return new_subscription, server
pass
# if not isinstance(response,MarzbanUser) or not isinstance(result,MarzbanUser):
# self.logger.error(f"Ошибка при добавлении клиента: {response}, {result}")
# return None
await self.postgres_repo.add_record(new_subscription)
self.logger.info(f"Подписка сохранена в БД с ID: {new_subscription.id}")
return new_subscription
except Exception as e:
self.logger.error(f"Неожиданная ошибка в _create_subscription_and_add_client: {str(e)}")
import traceback
self.logger.error(f"Трассировка: {traceback.format_exc()}")
return None
async def generate_uri(self, telegram_id: int):
"""
@@ -182,78 +216,24 @@ class DatabaseManager:
:param telegram_id: Telegram ID пользователя.
:return: Строка URI или None в случае ошибки.
"""
# try:
# # Извлечение данных
# subscription = await self.postgres_repo.get_active_subscription(telegram_id)
# if not subscription:
# self.logger.error(f"Подписки для пользователя {telegram_id} не найдены.")
# return "SUB_ERROR"
try:
user = await self.get_user_by_telegram_id(telegram_id)
if user == False or user == None:
self.logger.error(f"Ошибка при получении клиента: user = {user}")
return "ERROR"
result = await self.marzban_service.get_config_links(user)
if result == None:
self.logger.error(f"Ошибка при получении ссылки клиента: result = {user}")
return "ERROR"
self.logger.info(f"Итог generate_uri: result = {result}")
# server = await self.mongo_repo.get_server(subscription.vpn_server_id)
# if not server:
# self.logger.error(f"Сервер с ID {subscription.vpn_server_id} не найден в MongoDB.")
# return None
# user = await self.postgres_repo.get_user_by_telegram_id(telegram_id)
# if not user:
# self.logger.error(f"Пользователь с telegram_id {telegram_id} не найден.")
# return None
# email = user.username # Используем email из данных пользователя
# panel = PanelInteraction(
# base_url=f"https://{server['server']['ip']}:{server['server']['port']}/{server['server']['secretKey']}",
# login_data={"username": server["server"]["login"], "password": server["server"]["password"]},
# logger=self.logger,
# certificate=server["server"]["certificate"]["data"],
# )
# inbound_info = await panel.get_inbound_info(inbound_id=1) # Используем фиксированный ID
# if not inbound_info:
# self.logger.error(f"Не удалось получить информацию об инбаунде для ID {subscription.vpn_server_id}.")
# return None
# # Логируем полученные данные
# self.logger.info(f"Inbound Info: {inbound_info}")
# # Разбор JSON-строк
# try:
# stream_settings = json.loads(inbound_info["obj"]["streamSettings"])
# except KeyError as e:
# self.logger.error(f"Ключ 'streamSettings' отсутствует: {e}")
# return None
# except json.JSONDecodeError as e:
# self.logger.error(f"Ошибка разбора JSON для 'streamSettings': {e}")
# return None
# settings = json.loads(inbound_info["obj"]["settings"]) # Разбираем JSON
# # Находим клиента по email
# client = next((c for c in settings["clients"] if c["email"] == email), None)
# if not client:
# self.logger.error(f"Клиент с email {email} не найден среди клиентов.")
# return None
# server_info = server["server"]
# # Преобразование данных в формат URI
# uri = (
# f"vless://{client['id']}@{server_info['ip']}:443?"
# f"type={stream_settings['network']}&security={stream_settings['security']}"
# f"&pbk={stream_settings['realitySettings']['settings']['publicKey']}"
# f"&fp={stream_settings['realitySettings']['settings']['fingerprint']}"
# f"&sni={stream_settings['realitySettings']['serverNames'][0]}"
# f"&sid={stream_settings['realitySettings']['shortIds'][0]}"
# f"&spx=%2F&flow={client['flow']}"
# f"#{inbound_info['obj']['remark']}-{client['email']}"
# )
# self.logger.info(f"Сформирован URI для пользователя {telegram_id}: {uri}")
# return uri
# except Exception as e:
# self.logger.error(f"Ошибка при генерации URI для пользователя {telegram_id}: {e}")
# return None
pass
return result
except Exception as e:
self.logger.error(f"Неожиданная ошибка в generate_uri: {str(e)}")
return "ERROR"
@staticmethod
def generate_string(length):
@@ -261,3 +241,11 @@ class DatabaseManager:
Генерирует случайную строку заданной длины.
"""
return ''.join(random.choices(string.ascii_lowercase + string.digits, k=length))
def _is_subscription_expired(self, expire_timestamp: int) -> bool:
"""Проверяет, истекла ли подписка"""
current_time = datetime.now(timezone.utc)
expire_time = datetime.fromtimestamp(expire_timestamp, tz=timezone.utc)
return expire_time < current_time