diff --git a/app/routes/subscription_routes.py b/app/routes/subscription_routes.py index 949e6a2..807aad3 100644 --- a/app/routes/subscription_routes.py +++ b/app/routes/subscription_routes.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Optional from fastapi import APIRouter, HTTPException, Depends from pydantic import BaseModel from app.services import DatabaseManager @@ -15,15 +15,17 @@ router = APIRouter() class BuySubscriptionRequest(BaseModel): telegram_id: int - plan_id: str + plan_name: str class SubscriptionResponse(BaseModel): - id: str - plan: str - vpn_server_id: str - expiry_date: str + id: str + user_id: int + plan_name: str + vpn_server_id: Optional[str] + status: str + start_date: str + end_date: str created_at: str - updated_at: str # Эндпоинт для покупки подписки @router.post("/subscription/buy", response_model=dict) @@ -35,23 +37,34 @@ async def buy_subscription( Покупка подписки. """ try: - result = await database_manager.buy_sub(request_data.telegram_id, request_data.plan_id) + logger.info(f"Получен запрос на покупку подписки: {request_data.dict()}") + + result = await database_manager.buy_sub(request_data.telegram_id, request_data.plan_name) - if result == "ERROR": - raise HTTPException(status_code=500, detail="ERROR") + logger.info(f"Результат buy_sub: {result}") + + if result == "ERROR" or result is None: + raise HTTPException(status_code=500, detail="Internal server error") elif result == "INSUFFICIENT_FUNDS": raise HTTPException(status_code=400, detail="INSUFFICIENT_FUNDS") elif result == "TARIFF_NOT_FOUND": raise HTTPException(status_code=400, detail="TARIFF_NOT_FOUND") elif result == "ACTIVE_SUBSCRIPTION_EXISTS": raise HTTPException(status_code=400, detail="ACTIVE_SUBSCRIPTION_EXISTS") - result = await database_manager.generate_uri(request_data.telegram_id) - return {"message": result} + + # Если успешно, генерируем URI + if isinstance(result, dict) and result.get('status') == 'OK': + uri_result = await database_manager.generate_uri(request_data.telegram_id) + logger.info(f"Результат генерации URI: {uri_result}") + return {"status": "success", "subscription_id": result.get('subscription_id'), "uri": uri_result[0]} + else: + return {"status": "success", "message": "Subscription created"} + except HTTPException as http_exc: - # Пропускаем HTTPException, чтобы FastAPI обработал его корректно + logger.error(f"HTTPException в buy_subscription: {http_exc.detail}") raise http_exc except Exception as e: - # Обрабатываем остальные исключения + logger.error(f"Неожиданная ошибка в buy_subscription: {str(e)}") raise HTTPException(status_code=500, detail=f"Unexpected error: {str(e)}") @@ -63,21 +76,24 @@ async def last_subscription(telegram_id: int, database_manager: DatabaseManager """ logger.info(f"Получение последней подписки для пользователя: {telegram_id}") try: - subscriptions = await database_manager.get_last_subscriptions(telegram_id=telegram_id, limit=1) + subscription = await database_manager.get_last_subscriptions(telegram_id=telegram_id) - if not subscriptions: + plan = await database_manager.get_plan_by_id(subscription.plan_id) + + if not subscription or not plan: logger.warning(f"Подписки для пользователя {telegram_id} не найдены") raise HTTPException(status_code=404, detail="No subscriptions found") - sub = subscriptions[0] return { - "id": sub.id, - "plan": sub.plan, - "vpn_server_id": sub.vpn_server_id, - "expiry_date": sub.expiry_date.isoformat(), - "created_at": sub.created_at.isoformat(), - "updated_at": sub.updated_at.isoformat(), + "id": str(subscription.id), + "user_id": subscription.user_id, + "plan_name": plan.name, + "vpn_server_id": subscription.vpn_server_id, + "status": subscription.status.value, + "start_date": subscription.start_date.isoformat(), + "end_date": subscription.end_date.isoformat(), + "created_at": subscription.created_at.isoformat(), } except SQLAlchemyError as e: logger.error(f"Ошибка базы данных при получении подписки для пользователя {telegram_id}: {e}") @@ -135,10 +151,10 @@ async def get_uri(telegram_id: int, database_manager: DatabaseManager = Depends( if uri == "SUB_ERROR": raise HTTPException(status_code=404, detail="SUB_ERROR") if not uri: - logger.warning(f"Не удалось сгенерировать URI для пользователя с telegram_id {telegram_id}") + logger.warning(f"Не удалось сгенерировать URI для пользователя с telegram_id {telegram_id}, данные -> {uri}") raise HTTPException(status_code=404, detail="URI not found") - return {"detail": uri} + return {"detail": uri[0]} except HTTPException as e: # Пропускаем HTTPException, чтобы FastAPI обработал её автоматически diff --git a/app/services/db_manager.py b/app/services/db_manager.py index dd2c767..0ca4774 100644 --- a/app/services/db_manager.py +++ b/app/services/db_manager.py @@ -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 \ No newline at end of file diff --git a/app/services/marzban.py b/app/services/marzban.py index c4fec17..44ca13a 100644 --- a/app/services/marzban.py +++ b/app/services/marzban.py @@ -5,6 +5,10 @@ from datetime import date, datetime, time, timezone import logging from instance import User, Subscription +class UserAlreadyExistsError(Exception): + """Пользователь уже существует в системе""" + pass + class MarzbanUser: """Модель пользователя Marzban""" def __init__(self, data: Dict[str, Any]): @@ -78,12 +82,14 @@ class MarzbanService: ) as response: response_data = await response.json() if response.content_length else {} - + if response.status == 409: + raise UserAlreadyExistsError(f"User already exists: {response_data}") if response.status not in (200, 201): raise Exception(f"HTTP {response.status}: {response_data}") return response_data - + except UserAlreadyExistsError: + raise # Пробрасываем наверх except aiohttp.ClientError as e: logging.error(f"Network error during {method.upper()} to {url}: {e}") raise @@ -91,9 +97,9 @@ class MarzbanService: logging.error(f"Unexpected error during request to {url}: {e}") raise - async def create_user(self, user: User, subscription: Subscription) -> MarzbanUser: + async def create_user(self, user: User, subscription: Subscription) -> str | MarzbanUser: """Создает нового пользователя в Marzban""" - username = f"user_{user.telegram_id}" + logging.info(f"Конец подписки пользователя {user.telegram_id} {subscription.end_date}") if subscription.end_date: if isinstance(subscription.end_date, datetime): if subscription.end_date.tzinfo is None: @@ -114,7 +120,7 @@ class MarzbanService: expire_timestamp = 0 data = { - "username": username, + "username": user.username, "status": "active", "expire": expire_timestamp, "data_limit": 100 * 1073741824, # Конвертируем GB в bytes @@ -139,15 +145,18 @@ class MarzbanService: try: response_data = await self._make_request("/api/user", "post", data) marzban_user = MarzbanUser(response_data) - logging.info(f"User {username} created successfully") + logging.info(f"Пользователь {user.username} успешно создан в Marzban") return marzban_user + except UserAlreadyExistsError: + logging.warning(f"Пользователь {user.telegram_id} уже существует в Marzban") + return "USER_ALREADY_EXISTS" except Exception as e: - logging.error(f"Failed to create user {username}: {e}") + logging.error(f"Failed to create user {user.username}: {e}") raise Exception(f"Failed to create user: {e}") async def update_user(self, user: User, subscription: Subscription) -> MarzbanUser: """Обновляет существующего пользователя""" - username = f"user_{user.telegram_id}" + username = user.username if subscription.end_date: if isinstance(subscription.end_date, datetime): @@ -186,7 +195,7 @@ class MarzbanService: async def disable_user(self, user: User) -> bool: """Отключает пользователя""" - username = f"user_{user.telegram_id}" + username = user.username data = { "status": "disabled" @@ -202,7 +211,7 @@ class MarzbanService: async def enable_user(self, user: User) -> bool: """Включает пользователя""" - username = f"user_{user.telegram_id}" + username = user.username data = { "status": "active" @@ -218,41 +227,38 @@ class MarzbanService: async def delete_user(self, user: User) -> bool: """Полностью удаляет пользователя из Marzban""" - username = f"user_{user.telegram_id}" try: - await self._make_request(f"/api/user/{username}", "delete") - logging.info(f"User {username} deleted successfully") + await self._make_request(f"/api/user/{user.username}", "delete") + logging.info(f"User {user.username} deleted successfully") return True except Exception as e: - logging.error(f"Failed to delete user {username}: {e}") + logging.error(f"Failed to delete user {user.username}: {e}") return False async def get_user_status(self, user: User) -> UserStatus: """Получает текущий статус пользователя""" - username = f"user_{user.telegram_id}" try: - response_data = await self._make_request(f"/api/user/{username}", "get") + response_data = await self._make_request(f"/api/user/{user.username}", "get") return UserStatus(response_data) except Exception as e: - logging.error(f"Failed to get status for user {username}: {e}") + logging.error(f"Failed to get status for user {user.username}: {e}") raise Exception(f"Failed to get user status: {e}") - async def get_subscription_url(self, user: User) -> str: + async def get_subscription_url(self, user: User) -> str | None: """Возвращает готовую subscription_url для подключения""" - username = f"user_{user.telegram_id}" try: - response_data = await self._make_request(f"/api/user/{username}", "get") + response_data = await self._make_request(f"/api/user/{user.username}", "get") return response_data.get('subscription_url', '') except Exception as e: - logging.error(f"Failed to get subscription URL for user {username}: {e}") - return "" + logging.error(f"Failed to get subscription URL for user {user.username}: {e}") + return None async def get_config_links(self, user: User) -> str: """Возвращает конфигурации для подключения""" - username = f"user_{user.telegram_id}" + username = user.username try: response_data = await self._make_request(f"/api/user/{username}", "get") diff --git a/app/services/postgres_rep.py b/app/services/postgres_rep.py index 090211c..f7a5f6f 100644 --- a/app/services/postgres_rep.py +++ b/app/services/postgres_rep.py @@ -6,7 +6,7 @@ from sqlalchemy.exc import SQLAlchemyError from decimal import Decimal from sqlalchemy import asc, desc, update from sqlalchemy.orm import joinedload -from instance.model import Referral, User, Subscription, Transaction +from instance.model import Referral, User, Subscription, Transaction, Plan class PostgresRepository: @@ -37,12 +37,18 @@ class PostgresRepository: try: result = await session.execute( select(Subscription) - .join(User, Subscription.user_id == User.id) - .where(User.telegram_id == telegram_id, Subscription.expiry_date > datetime.utcnow()) + .join(User, Subscription.user_id == User.telegram_id) + .where(User.telegram_id == telegram_id, Subscription.end_date > datetime.utcnow()) ) - result= result.scalars().first() - self.logger.info(f"Пользователь с id {telegram_id}, проверен и имеет {result}") - return result + subscription = result.scalars().first() + if subscription: + # Отделяем объект от сессии + session.expunge(subscription) + self.logger.info(f"Пользователь с id {telegram_id}, проверен и имеет подписку ID: {subscription.id}") + else: + self.logger.info(f"Пользователь с id {telegram_id}, проверен и имеет None") + + return subscription except SQLAlchemyError as e: self.logger.error(f"Ошибка проверки активной подписки для пользователя {telegram_id}: {e}") return None @@ -104,7 +110,7 @@ class PostgresRepository: self.logger.error(f"Ошибка получения транзакций пользователя {user_telegram_id}: {e}") return None - async def get_last_subscription_by_user_id(self, user_telegram_id: int, limit: int = 1): + async def get_last_subscription_by_user_id(self, user_telegram_id: int): """ Извлекает последнюю подписку пользователя на основании user_id. @@ -117,12 +123,19 @@ class PostgresRepository: select(Subscription) .where(Subscription.user_id == user_telegram_id) .order_by(desc(Subscription.created_at)) - .limit(limit) + .limit(1) ) - subscriptions = list(result.scalars()) - result.scalars() - self.logger.info(f"Найдены такие подписки: {subscriptions}") - return subscriptions + subscription = result.scalars().first() + self.logger.info(f"Найдены такие подписки: {subscription}") + + if subscription: + session.expunge(subscription) + self.logger.info(f"Найдена подписка ID: {subscription.id} для пользователя {user_telegram_id}") + return subscription + + else: + return None + except SQLAlchemyError as e: self.logger.error(f"Ошибка при получении подписки для пользователя {user_telegram_id}: {e}") return None @@ -142,7 +155,7 @@ class PostgresRepository: except SQLAlchemyError as e: self.logger.error(f"Ошибка при добавлении записи: {record}: {e}") await session.rollback() - return None + raise Exception async def add_referral(self, referrer_id: int, referral_id: int): """ @@ -198,4 +211,42 @@ class PostgresRepository: except Exception as e: await session.rollback() self.logger.error(f"Ошибка при добавлении реферальной связи: {str(e)}") - raise \ No newline at end of file + raise + + async def get_subscription_plan(self, plan_name:str) -> Plan | None: + """ + Поиск плана для подписки + + :param plan_name: Объект записи. + :return: Запись или None в случае ошибки. + """ + async for session in self.session_generator(): + try: + result = await session.execute( + select(Plan) + .where(Plan.name == plan_name) + ) + return result.scalar_one_or_none() + except SQLAlchemyError as e: + self.logger.error(f"Ошибка при поиске плана: {plan_name}: {e}") + await session.rollback() + return Noneэ + + async def get_plan_by_id(self, plan_id: int) -> Plan | None: + """ + Поиск плана для подписки + + :param plan_name: Объект записи. + :return: Запись или None в случае ошибки. + """ + async for session in self.session_generator(): + try: + result = await session.execute( + select(Plan) + .where(Plan.id == plan_id) + ) + return result.scalar_one_or_none() + except SQLAlchemyError as e: + self.logger.error(f"Ошибка при поиске плана: {plan_id}: {e}") + await session.rollback() + return None \ No newline at end of file diff --git a/instance/configdb.py b/instance/configdb.py index 1d0ae6a..4cc11b6 100644 --- a/instance/configdb.py +++ b/instance/configdb.py @@ -6,9 +6,11 @@ from .model import Base try: # Настройки PostgreSQL из переменных окружения POSTGRES_DSN = os.getenv("POSTGRES_URL") - + BASE_URL_MARZBAN = os.getenv("BASE_URL_MARZBAN") + USERNAME_MARZBA = os.getenv('USERNAME_MARZBAN') + PASSWORD_MARZBAN = os.getenv('PASSWORD_MARZBAN') # Создание движка для PostgreSQL - if POSTGRES_DSN is None: + if POSTGRES_DSN is None or BASE_URL_MARZBAN is None or USERNAME_MARZBA is None or PASSWORD_MARZBAN is None: raise Exception postgres_engine = create_async_engine(POSTGRES_DSN, echo=False) except Exception as e: @@ -49,4 +51,4 @@ def get_database_manager() -> DatabaseManager: """ Функция-зависимость для получения экземпляра DatabaseManager. """ - return DatabaseManager(get_postgres_session) + return DatabaseManager(get_postgres_session, USERNAME_MARZBA,PASSWORD_MARZBAN,BASE_URL_MARZBAN) diff --git a/main.py b/main.py index 31305a2..6ac4e6e 100644 --- a/main.py +++ b/main.py @@ -1,3 +1,4 @@ +import os import sys from fastapi import FastAPI from instance import setup_logging @@ -10,15 +11,13 @@ setup_logging() # force=True # ) -from instance import init_postgresql, close_connections, get_postgres_session +from instance import init_postgresql, close_connections from app.routes import router, subscription_router -from app.services import DatabaseManager logger = logging.getLogger(__name__) app = FastAPI() -database_manager = DatabaseManager(session_generator=get_postgres_session) @app.on_event("startup") async def startup():