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

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,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 обработал её автоматически

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

View File

@@ -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")

View File

@@ -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
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