Рефакторинга чуть чуть вроде

This commit is contained in:
Disledg
2025-01-07 08:30:17 +03:00
parent 63f7251860
commit 54bfedd6f2
6 changed files with 360 additions and 307 deletions

View File

@@ -38,17 +38,21 @@ async def buy_subscription(
result = await database_manager.buy_sub(request_data.telegram_id, request_data.plan_id) result = await database_manager.buy_sub(request_data.telegram_id, request_data.plan_id)
if result == "ERROR": if result == "ERROR":
raise HTTPException(status_code=500, detail="Failed to buy subscription") raise HTTPException(status_code=500, detail="ERROR")
elif result == "INSUFFICIENT_FUNDS": elif result == "INSUFFICIENT_FUNDS":
raise HTTPException(status_code=400, detail="Insufficient funds") raise HTTPException(status_code=400, detail="INSUFFICIENT_FUNDS")
elif result == "TARIFF_NOT_FOUND": elif result == "TARIFF_NOT_FOUND":
raise HTTPException(status_code=400, detail="Tariff not found") raise HTTPException(status_code=400, detail="TARIFF_NOT_FOUND")
elif result == "ACTIVE_SUBSCRIPTION_EXISTS": elif result == "ACTIVE_SUBSCRIPTION_EXISTS":
raise HTTPException(status_code=400, detail="User already had subscription",) raise HTTPException(status_code=400, detail="ACTIVE_SUBSCRIPTION_EXISTS")
result = await database_manager.generate_uri(request_data.telegram_id)
return {"message": "Subscription purchased successfully"} return {"message": result}
except HTTPException as http_exc:
# Пропускаем HTTPException, чтобы FastAPI обработал его корректно
raise http_exc
except Exception as e: except Exception as e:
raise HTTPException(status_code=500, detail=str(e)) # Обрабатываем остальные исключения
raise HTTPException(status_code=500, detail=f"Unexpected error: {str(e)}")
# Эндпоинт для получения последней подписки # Эндпоинт для получения последней подписки
@@ -59,11 +63,12 @@ async def last_subscription(user_id: UUID, database_manager: DatabaseManager = D
""" """
logger.info(f"Получение последней подписки для пользователя: {user_id}") logger.info(f"Получение последней подписки для пользователя: {user_id}")
try: try:
subscriptions = await database_manager.last_subscriptions(user_id=str(user_id), limit=1) subscriptions = await database_manager.get_last_subscriptions(user_id=user_id, limit=1)
if not subscriptions: if not subscriptions:
logger.warning(f"Подписки для пользователя {user_id} не найдены") logger.warning(f"Подписки для пользователя {user_id} не найдены")
raise HTTPException(status_code=404, detail="No subscriptions found") raise HTTPException(status_code=404, detail="No subscriptions found")
sub = subscriptions[0] sub = subscriptions[0]
return { return {
@@ -77,9 +82,12 @@ async def last_subscription(user_id: UUID, database_manager: DatabaseManager = D
except SQLAlchemyError as e: except SQLAlchemyError as e:
logger.error(f"Ошибка базы данных при получении подписки для пользователя {user_id}: {e}") logger.error(f"Ошибка базы данных при получении подписки для пользователя {user_id}: {e}")
raise HTTPException(status_code=500, detail="Database error") raise HTTPException(status_code=500, detail="Database error")
except HTTPException as e:
# Пропускаем HTTPException, чтобы FastAPI обработал её автоматически
raise e
except Exception as e: except Exception as e:
logger.error(f"Неожиданная ошибка: {e}") logger.error(f"Неожиданная ошибка: {e}")
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail="Internal Server Error")
@router.get("/subscriptions/{user_id}", response_model=List[SubscriptionResponse]) @router.get("/subscriptions/{user_id}", response_model=List[SubscriptionResponse])
async def get_subscriptions(user_id: UUID, database_manager: DatabaseManager = Depends(get_database_manager)): async def get_subscriptions(user_id: UUID, database_manager: DatabaseManager = Depends(get_database_manager)):
@@ -114,3 +122,25 @@ async def get_subscriptions(user_id: UUID, database_manager: DatabaseManager = D
logger.error(f"Неожиданная ошибка: {e}") logger.error(f"Неожиданная ошибка: {e}")
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
@router.get("/uri", response_model=dict)
async def get_uri(telegram_id: int, database_manager: DatabaseManager = Depends(get_database_manager)):
"""
Возвращает список подписок пользователя.
"""
logger.info(f"Получение подписок для пользователя: {telegram_id}")
try:
# Получаем подписки без ограничений или с указанным лимитом
uri = await database_manager.generate_uri(telegram_id)
if uri == "SUB_ERROR":
raise HTTPException(status_code=404, detail="User not found")
if not uri:
logger.warning(f"Не удалось сгенерировать URI для пользователя с telegram_id {telegram_id}")
raise HTTPException(status_code=404, detail="URI not found")
return {"detail": uri }
except SQLAlchemyError as e:
logger.error(f"Ошибка базы данных при получении подписок для пользователя {telegram_id}: {e}")
raise HTTPException(status_code=500, detail="Database error")
except Exception as e:
logger.error(f"Неожиданная ошибка: {e}")
raise HTTPException(status_code=500, detail=str(e))

View File

@@ -43,8 +43,6 @@ def handle_exception(e: Exception, message: str):
logger.error(f"{message}: {e}") logger.error(f"{message}: {e}")
raise HTTPException(status_code=500, detail=f"{message}: {str(e)}") raise HTTPException(status_code=500, detail=f"{message}: {str(e)}")
@router.post("/support/tickets/{ticket_id}/messages", response_model=TicketMessageResponse, summary="Добавить сообщение") @router.post("/support/tickets/{ticket_id}/messages", response_model=TicketMessageResponse, summary="Добавить сообщение")
async def add_message( async def add_message(
ticket_id: int, ticket_id: int,

View File

@@ -1,8 +1,14 @@
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException
from fastapi.exceptions import HTTPException
from app.services.db_manager import DatabaseManager from app.services.db_manager import DatabaseManager
from sqlalchemy.exc import SQLAlchemyError
from instance.configdb import get_database_manager from instance.configdb import get_database_manager
from pydantic import BaseModel from pydantic import BaseModel
from uuid import UUID from uuid import UUID
import logging
logger = logging.getLogger(__name__)
router = APIRouter()
router = APIRouter() router = APIRouter()
@@ -11,7 +17,7 @@ class CreateUserRequest(BaseModel):
telegram_id: int telegram_id: int
class UserResponse(BaseModel): class UserResponse(BaseModel):
id: str id: UUID
telegram_id: int telegram_id: int
username: str username: str
balance: float balance: float
@@ -44,6 +50,7 @@ async def create_user(
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
@router.get("/user/{telegram_id}", response_model=UserResponse, summary="Получить информацию о пользователе") @router.get("/user/{telegram_id}", response_model=UserResponse, summary="Получить информацию о пользователе")
async def get_user( async def get_user(
telegram_id: int, telegram_id: int,
@@ -53,11 +60,15 @@ async def get_user(
Получение информации о пользователе. Получение информации о пользователе.
""" """
try: try:
logger.info(f"Получение пользователя с telegram_id: {telegram_id}")
user = await db_manager.get_user_by_telegram_id(telegram_id) user = await db_manager.get_user_by_telegram_id(telegram_id)
if not user: if not user:
logger.warning(f"Пользователь с telegram_id {telegram_id} не найден.")
raise HTTPException(status_code=404, detail="User not found") raise HTTPException(status_code=404, detail="User not found")
return UserResponse( logger.info(f"Пользователь найден: ID={user.id}, Username={user.username}")
user_response = UserResponse(
id=user.id, id=user.id,
telegram_id=user.telegram_id, telegram_id=user.telegram_id,
username=user.username, username=user.username,
@@ -65,9 +76,23 @@ async def get_user(
created_at=user.created_at.isoformat(), created_at=user.created_at.isoformat(),
updated_at=user.updated_at.isoformat() updated_at=user.updated_at.isoformat()
) )
logger.debug(f"Формирование ответа для пользователя: {user_response}")
return user_response
except HTTPException as http_ex: # Позволяет обработать HTTPException отдельно
raise http_ex
except SQLAlchemyError as e:
logger.error(f"Ошибка базы данных при получении пользователя с telegram_id {telegram_id}: {e}")
raise HTTPException(status_code=500, detail="Database error")
except Exception as e: except Exception as e:
logger.exception(f"Неожиданная ошибка при получении пользователя с telegram_id {telegram_id}: {e}")
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
@router.post("/user/{telegram_id}/balance/{amount}", summary="Обновить баланс") @router.post("/user/{telegram_id}/balance/{amount}", summary="Обновить баланс")
async def update_balance( async def update_balance(
telegram_id: int, telegram_id: int,
@@ -77,14 +102,26 @@ async def update_balance(
""" """
Обновляет баланс пользователя. Обновляет баланс пользователя.
""" """
logger.info(f"Получен запрос на обновление баланса: telegram_id={telegram_id}, amount={amount}")
try: try:
result = await db_manager.update_balance(telegram_id, amount) result = await db_manager.update_balance(telegram_id, amount)
if result == "ERROR": if result == "ERROR":
logger.error(f"Ошибка обновления баланса для пользователя {telegram_id}")
raise HTTPException(status_code=500, detail="Failed to update balance") raise HTTPException(status_code=500, detail="Failed to update balance")
logger.info(f"Баланс пользователя {telegram_id} успешно обновлен на {amount}")
return {"message": "Balance updated successfully"} return {"message": "Balance updated successfully"}
except HTTPException as http_ex:
logger.warning(f"HTTP ошибка: {http_ex.detail}")
raise http_ex
except SQLAlchemyError as db_ex:
logger.error(f"Ошибка базы данных при обновлении баланса пользователя {telegram_id}: {db_ex}")
raise HTTPException(status_code=500, detail="Database error")
except Exception as e: except Exception as e:
logger.exception(f"Неожиданная ошибка при обновлении баланса пользователя {telegram_id}: {e}")
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))
@router.get("/user/{user_id}/transactions", summary="Последние транзакции пользователя") @router.get("/user/{user_id}/transactions", summary="Последние транзакции пользователя")
async def last_transactions( async def last_transactions(
user_id: UUID, user_id: UUID,
@@ -93,11 +130,17 @@ async def last_transactions(
""" """
Возвращает список последних транзакций пользователя. Возвращает список последних транзакций пользователя.
""" """
logger.info(f"Получен запрос на транзакции для пользователя: {user_id}")
try: try:
transactions = await db_manager.last_transaction(user_id) logger.debug(f"Вызов метода get_transaction с user_id={user_id}")
transactions = await db_manager.get_transaction(user_id)
if transactions == "ERROR": if transactions == "ERROR":
logger.error(f"Ошибка при получении транзакций для пользователя: {user_id}")
raise HTTPException(status_code=500, detail="Failed to fetch transactions") raise HTTPException(status_code=500, detail="Failed to fetch transactions")
return [
logger.debug(f"Транзакции для {user_id}: {transactions}")
response = [
{ {
"id": tx.id, "id": tx.id,
"amount": tx.amount, "amount": tx.amount,
@@ -105,5 +148,17 @@ async def last_transactions(
"transaction_type": tx.transaction_type, "transaction_type": tx.transaction_type,
} for tx in transactions } for tx in transactions
] ]
logger.info(f"Формирование ответа для пользователя {user_id}: {response}")
return response
except HTTPException as http_ex:
logger.warning(f"HTTP ошибка для {user_id}: {http_ex.detail}")
raise http_ex
except SQLAlchemyError as db_ex:
logger.error(f"Ошибка базы данных для {user_id}: {db_ex}")
raise HTTPException(status_code=500, detail="Database error")
except Exception as e: except Exception as e:
logger.exception(f"Неожиданная ошибка для {user_id}: {e}")
raise HTTPException(status_code=500, detail=str(e)) raise HTTPException(status_code=500, detail=str(e))

View File

@@ -1,168 +1,118 @@
from instance.model import User, Subscription, Transaction, Administrators, SupportTicket,TicketMessage,TicketStatus from decimal import Decimal
from sqlalchemy.future import select import json
from sqlalchemy.exc import SQLAlchemyError from instance.model import User, Subscription, Transaction, SupportTicket, TicketMessage, TicketStatus
from sqlalchemy import desc from .xui_rep import PanelInteraction
from .postgres_rep import PostgresRepository
from .mongo_rep import MongoDBRepository
from instance.model import Transaction
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from datetime import datetime from datetime import datetime
from .xui_rep import PanelInteraction
from .mongo_rep import MongoDBRepository
import random import random
import string import string
import logging import logging
from uuid import UUID from uuid import UUID
class DatabaseManager: class DatabaseManager:
def __init__(self, session_generator): def __init__(self, session_generator):
""" """
Инициализация с асинхронным генератором сессий (например, get_postgres_session). Инициализация с асинхронным генератором сессий (например, get_postgres_session).
""" """
self.session_generator = session_generator
self.logger = logging.getLogger(__name__) self.logger = logging.getLogger(__name__)
self.mongo_repo = MongoDBRepository() self.mongo_repo = MongoDBRepository()
self.postgres_repo = PostgresRepository(session_generator, self.logger)
async def create_user(self, telegram_id: int): async def create_user(self, telegram_id: int):
""" """
Создаёт нового пользователя, если его нет. Создаёт пользователя.
""" """
async for session in self.session_generator(): username = self.generate_string(6)
try: return await self.postgres_repo.create_user(telegram_id, username)
username = self.generate_string(6)
result = await session.execute(select(User).where(User.telegram_id == int(telegram_id)))
user = result.scalars().first()
if not user:
new_user = User(telegram_id=int(telegram_id), username=username)
session.add(new_user)
await session.commit()
return new_user
return user
except SQLAlchemyError as e:
self.logger.error(f"Ошибка при создании пользователя {telegram_id}: {e}")
await session.rollback()
return "ERROR"
async def get_user_by_telegram_id(self, telegram_id: int): async def get_user_by_telegram_id(self, telegram_id: int):
""" """
Возвращает пользователя по Telegram ID. Возвращает пользователя по Telegram ID.
""" """
async for session in self.session_generator(): return await self.postgres_repo.get_user_by_telegram_id(telegram_id)
try:
result = await session.execute(select(User).where(User.telegram_id == telegram_id))
return result.scalars().first()
except SQLAlchemyError as e:
self.logger.error(f"Ошибка при получении пользователя {telegram_id}: {e}")
return None
async def add_transaction(self, user_id: int, amount: float): async def add_transaction(self, user_id: UUID, amount: float):
""" """
Добавляет транзакцию для пользователя. Добавляет транзакцию.
""" """
async for session in self.session_generator(): tran = Transaction(
try: user_id=user_id,
transaction = Transaction(user_id=user_id, amount=amount) amount=Decimal(amount),
session.add(transaction) transaction_type="default"
await session.commit() )
except SQLAlchemyError as e: return await self.postgres_repo.add_record(tran)
self.logger.error(f"Ошибка добавления транзакции для пользователя {user_id}: {e}")
await session.rollback() async def get_transaction(self, user_id: UUID, limit: int = 10):
"""
Возвращает транзакции.
"""
return await self.postgres_repo.get_last_transactions(user_id, limit)
async def update_balance(self, telegram_id: int, amount: float): async def update_balance(self, telegram_id: int, amount: float):
""" """
Обновляет баланс пользователя и добавляет транзакцию. Обновляет баланс пользователя и добавляет транзакцию.
""" """
async for session in self.session_generator(): self.logger.info(f"Попытка обновления баланса: telegram_id={telegram_id}, amount={amount}")
try: user = await self.get_user_by_telegram_id(telegram_id)
result = await session.execute(select(User).where(User.telegram_id == telegram_id)) if not user:
user = result.scalars().first() self.logger.warning(f"Пользователь с Telegram ID {telegram_id} не найден.")
if user: return "ERROR"
user.balance += int(amount)
await self.add_transaction(user.id, amount)
await session.commit()
else:
self.logger.warning(f"Пользователь с Telegram ID {telegram_id} не найден.")
return "ERROR"
except SQLAlchemyError as e:
self.logger.error(f"Ошибка при обновлении баланса: {e}")
await session.rollback()
return "ERROR"
async def last_subscriptions(self, user_id: str, limit: int = 10): updated = await self.postgres_repo.update_balance(user, amount)
""" if not updated:
Возвращает список последних подписок пользователя, ограниченный заданным количеством. self.logger.error(f"Не удалось обновить баланс пользователя {telegram_id}")
:param user_id: ID пользователя return "ERROR"
:param limit: Максимальное количество подписок для возврата
""" self.logger.info(f"Баланс пользователя {telegram_id} обновлен на {amount}, добавление транзакции")
async for session in self.session_generator(): await self.add_transaction(user.id, amount)
try: return "OK"
result = await session.execute(
select(Subscription)
.where(Subscription.user_id == str(user_id))
.order_by(desc(Subscription.created_at))
.limit(limit) # Ограничиваем количество результатов
)
subscriptions = result.scalars().all() # Получаем все результаты до лимита
if subscriptions:
return subscriptions
else:
self.logger.info(f"Для пользователя {user_id} подписки не найдены.")
return [] # Возвращаем пустой список, если подписок нет
except SQLAlchemyError as e:
self.logger.error(f"Ошибка при получении подписок для пользователя {user_id}: {e}")
return "ERROR"
async def get_active_subscription(self, telegram_id: int):
"""
Проверяет наличие активной подписки.
"""
return await self.postgres_repo.get_active_subscription(telegram_id)
async def last_transaction(self, user_id: UUID): async def get_last_subscriptions(self, user_id: UUID, limit: int ):
""" """
Возвращает список транзакций пользователя. Возвращает список последних подписок.
""" """
async for session in self.session_generator(): return await self.postgres_repo.get_last_subscription_by_user_id(user_id, limit)
try:
result = await session.execute(
select(Transaction)
.where(Transaction.user_id == str(user_id))
.order_by(desc(Transaction.created_at))
)
transactions = result.scalars().all()
return transactions
except SQLAlchemyError as e:
self.logger.error(f"Ошибка при получении транзакций пользователя {user_id}: {e}")
return "ERROR"
async def buy_sub(self, telegram_id: str, plan_id: str): async def buy_sub(self, telegram_id: str, plan_id: str):
async for session in self.session_generator(): """
try: Покупает подписку.
active_subscription = await self._check_active_subscription(telegram_id, session) """
if active_subscription: active_subscription = await self.get_active_subscription(telegram_id)
self.logger.error(f"Пользователь {telegram_id} уже имеет активную подписку.") self.logger.info(f"{active_subscription}")
return "ACTIVE_SUBSCRIPTION_EXISTS" if active_subscription:
result = await self._initialize_user_and_plan(telegram_id, plan_id) self.logger.error(f"Пользователь {telegram_id} уже имеет активную подписку.")
if isinstance(result, str): return "ACTIVE_SUBSCRIPTION_EXISTS"
return result # Возвращает "ERROR", "TARIFF_NOT_FOUND" или "INSUFFICIENT_FUNDS"
user, plan = result result = await self._initialize_user_and_plan(telegram_id, plan_id)
user.balance -= int(plan["price"]) if isinstance(result, str):
session.add(user) return result # Возвращает "ERROR", "TARIFF_NOT_FOUND" или "INSUFFICIENT_FUNDS"
new_subscription, server = await self._create_subscription_and_add_client(user, plan, session) user, plan = result
if not new_subscription: await self.postgres_repo.update_balance(user,-plan['price'])
await session.rollback() new_subscription, server = await self._create_subscription_and_add_client(user, plan)
return "ERROR"
await session.commit() if not new_subscription:
self.logger.info(f"Подписка успешно оформлена для пользователя {telegram_id} на план {plan_id} и клиент добавлен на сервер.") return "ERROR"
return "OK"
except SQLAlchemyError as e: self.logger.info(f"Подписка успешно оформлена для пользователя {telegram_id}.")
self.logger.error(f"Ошибка при покупке подписки {plan_id} для пользователя {telegram_id}: {e}") return "OK"
await session.rollback()
return "ERROR"
except Exception as e:
self.logger.error(f"Непредвиденная ошибка: {e}")
await session.rollback()
return "ERROR"
async def _initialize_user_and_plan(self, telegram_id, plan_id): async def _initialize_user_and_plan(self, telegram_id, plan_id):
user = await self.create_user(telegram_id) """
Инициализирует пользователя и план подписки.
"""
user = await self.get_user_by_telegram_id(telegram_id)
if not user: if not user:
self.logger.error(f"Пользователь с Telegram ID {telegram_id} не найден.") self.logger.error(f"Пользователь с Telegram ID {telegram_id} не найден.")
return "ERROR" return "ERROR"
@@ -179,157 +129,134 @@ class DatabaseManager:
return user, plan return user, plan
async def _create_subscription_and_add_client(self, user, plan, session): async def _create_subscription_and_add_client(self, user, plan):
"""
Создаёт подписку и добавляет клиента на сервер.
"""
expiry_date = datetime.utcnow() + relativedelta(months=plan["duration_months"]) expiry_date = datetime.utcnow() + relativedelta(months=plan["duration_months"])
server = await self.mongo_repo.get_server_with_least_clients() server = await self.mongo_repo.get_server_with_least_clients()
self.logger.info(f"Выбран сервер для подписки: {server}") if not server:
self.logger.error("Нет доступных серверов для подписки.")
return None, None
new_subscription = Subscription( new_subscription = Subscription(
user_id=user.id, user_id=user.id,
vpn_server_id=str(server['server']["name"]), vpn_server_id=str(server["server"]["name"]),
plan=plan["name"], plan=plan["name"],
expiry_date=expiry_date expiry_date=expiry_date,
) )
session.add(new_subscription)
server_data = await self.mongo_repo.get_server(new_subscription.vpn_server_id) panel = PanelInteraction(
if not server_data: base_url=f"https://{server['server']['ip']}:{server['server']['port']}/{server['server']['secretKey']}",
self.logger.error(f"Не удалось найти сервер с ID {new_subscription.vpn_server_id}.") login_data={"username": server["server"]["login"], "password": server["server"]["password"]},
return None, None logger=self.logger,
certificate=server["server"]["certificate"]["data"],
)
server_info = server_data['server'] response = await panel.add_client(
url_base = f"https://{server_info['ip']}:{server_info['port']}/{server_info['secretKey']}" inbound_id=1,
login_data = { expiry_date=expiry_date.isoformat(),
'username': server_info['login'], email=user.username,
'password': server_info['password'], )
}
panel = PanelInteraction(url_base, login_data, self.logger, server_info['certificate']['data'])
expiry_date_iso = new_subscription.expiry_date.isoformat()
response = await panel.add_client(1, expiry_date_iso, user.username)
if response != "OK": if response != "OK":
self.logger.error(f"Ошибка при добавлении клиента {user.telegram_id} на сервер: {response}") self.logger.error(f"Ошибка при добавлении клиента: {response}")
return None, None return None, None
await self.postgres_repo.add_record(new_subscription)
return new_subscription, server return new_subscription, server
async def _check_active_subscription(self, telegram_id, session):
async def generate_uri(self, telegram_id: int):
""" """
Проверяет наличие активной подписки у пользователя. Генерация URI для пользователя.
:param telegram_id: Telegram ID пользователя. :param telegram_id: Telegram ID пользователя.
:param session: Текущая сессия базы данных. :return: Строка URI или None в случае ошибки.
:return: Объект подписки или None.
""" """
try: try:
result = await session.execute( # Извлечение данных
select(Subscription) subscription = await self.postgres_repo.get_active_subscription(telegram_id)
.join(User, Subscription.user_id == User.id) if not subscription:
.where(User.telegram_id == telegram_id, Subscription.expiry_date > datetime.utcnow()) self.logger.error(f"Подписки для пользователя {telegram_id} не найдены.")
return "SUB_ERROR"
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"],
) )
return result.scalars().first()
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: except Exception as e:
self.logger.error(f"Ошибка проверки активной подписки для пользователя {telegram_id}: {e}") self.logger.error(f"Ошибка при генерации URI для пользователя {telegram_id}: {e}")
return None return None
async def add_ticket_message(self, ticket_id: int, sender: str, message: str):
"""
Добавляет сообщение к тикету.
"""
async for session in self.session_generator():
try:
self.logger.info(f"Попытка добавления сообщения в тикет {ticket_id} от {sender}")
ticket_message = TicketMessage(ticket_id=ticket_id, sender=sender, message=message)
session.add(ticket_message)
await session.commit()
self.logger.info(f"Сообщение добавлено к тикету {ticket_id}: {message}")
return ticket_message
except SQLAlchemyError as e:
self.logger.error(f"Ошибка при добавлении сообщения в тикет {ticket_id}: {e}")
await session.rollback()
return None
async def get_ticket_messages(self, ticket_id: int):
"""
Возвращает список сообщений для указанного тикета.
"""
async for session in self.session_generator():
try:
self.logger.info(f"Получение сообщений для тикета {ticket_id}")
result = await session.execute(
select(TicketMessage).where(TicketMessage.ticket_id == ticket_id).order_by(TicketMessage.created_at)
)
messages = result.scalars().all()
self.logger.info(f"Найдено {len(messages)} сообщений для тикета {ticket_id}")
return messages
except SQLAlchemyError as e:
self.logger.error(f"Ошибка при получении сообщений для тикета {ticket_id}: {e}")
return None
async def create_ticket(self, user_id: int, subject: str, message: str):
"""
Создаёт новый тикет.
"""
async for session in self.session_generator():
try:
self.logger.info(f"Создание тикета для пользователя {user_id}: {subject}")
ticket = SupportTicket(user_id=user_id, subject=subject, message=message)
session.add(ticket)
await session.commit()
self.logger.info(f"Тикет создан с ID {ticket.id} для пользователя {user_id}")
return ticket
except SQLAlchemyError as e:
self.logger.error(f"Ошибка при создании тикета: {e}")
await session.rollback()
return None
async def list_tickets(self, user_id: int):
"""
Возвращает список тикетов пользователя.
"""
async for session in self.session_generator():
try:
self.logger.info(f"Получение списка тикетов для пользователя {user_id}")
result = await session.execute(
select(SupportTicket).where(SupportTicket.user_id == user_id)
)
tickets = result.scalars().all()
self.logger.info(f"Найдено {len(tickets)} тикетов для пользователя {user_id}")
return tickets
except SQLAlchemyError as e:
self.logger.error(f"Ошибка при получении тикетов для пользователя {user_id}: {e}")
return None
async def update_ticket_status(self, ticket_id: int, status: TicketStatus):
"""
Обновляет статус тикета.
"""
async for session in self.session_generator():
try:
self.logger.info(f"Попытка обновления статуса тикета {ticket_id} на {status}")
result = await session.execute(
select(SupportTicket).where(SupportTicket.id == ticket_id)
)
ticket = result.scalars().first()
if ticket:
ticket.status = status
await session.commit()
self.logger.info(f"Статус тикета {ticket_id} обновлён на {status}")
return ticket
self.logger.warning(f"Тикет с ID {ticket_id} не найден для обновления статуса")
return None
except SQLAlchemyError as e:
self.logger.error(f"Ошибка при обновлении статуса тикета {ticket_id}: {e}")
await session.rollback()
return None
async def create_ticket(self, user_id: UUID, subject: str, message: str):
"""
Создаёт тикет
"""
ticket = SupportTicket(user_id=user_id,subject=subject,message=message)
return await self.postgres_repo.add_record(ticket)
@staticmethod @staticmethod
def generate_string(length): def generate_string(length):
""" """
Генерирует случайную строку заданной длины. Генерирует случайную строку заданной длины.
""" """
characters = string.ascii_lowercase + string.digits return ''.join(random.choices(string.ascii_lowercase + string.digits, k=length))
return ''.join(random.choices(characters, k=length))

View File

@@ -1,7 +1,10 @@
from datetime import datetime
from uuid import UUID
from sqlalchemy.future import select from sqlalchemy.future import select
from sqlalchemy.exc import SQLAlchemyError from sqlalchemy.exc import SQLAlchemyError
from decimal import Decimal
from sqlalchemy import desc from sqlalchemy import desc
from instance.model import User, Subscription, Transaction from instance.model import TicketMessage, User, Subscription, Transaction
class PostgresRepository: class PostgresRepository:
@@ -23,6 +26,23 @@ class PostgresRepository:
self.logger.error(f"Ошибка при создании пользователя {telegram_id}: {e}") self.logger.error(f"Ошибка при создании пользователя {telegram_id}: {e}")
await session.rollback() await session.rollback()
return None return None
async def get_active_subscription(self, telegram_id: int):
"""
Проверяет наличие активной подписки у пользователя.
"""
async for session in self.session_generator():
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())
)
result= result.scalars().first()
self.logger.info(f"Пользователь с id {telegram_id}, проверен и имеет {result}")
return result
except SQLAlchemyError as e:
self.logger.error(f"Ошибка проверки активной подписки для пользователя {telegram_id}: {e}")
return None
async def get_user_by_telegram_id(self, telegram_id: int): async def get_user_by_telegram_id(self, telegram_id: int):
""" """
@@ -31,61 +51,40 @@ class PostgresRepository:
async for session in self.session_generator(): async for session in self.session_generator():
try: try:
result = await session.execute(select(User).where(User.telegram_id == telegram_id)) result = await session.execute(select(User).where(User.telegram_id == telegram_id))
return result.scalars().first() if result:
return result.scalars().first()
return False
except SQLAlchemyError as e: except SQLAlchemyError as e:
self.logger.error(f"Ошибка при получении пользователя {telegram_id}: {e}") self.logger.error(f"Ошибка при получении пользователя {telegram_id}: {e}")
return None return False
async def add_transaction(self, user_id: int, amount: float):
"""
Добавляет транзакцию для пользователя.
"""
async for session in self.session_generator():
try:
transaction = Transaction(user_id=user_id, amount=amount)
session.add(transaction)
await session.commit()
except SQLAlchemyError as e:
self.logger.error(f"Ошибка добавления транзакции для пользователя {user_id}: {e}")
await session.rollback()
async def update_balance(self, telegram_id: int, amount: float): async def update_balance(self, user: User, amount: float):
""" """
Обновляет баланс пользователя. Обновляет баланс пользователя.
:param user: Объект пользователя.
:param amount: Сумма для добавления/вычитания.
:return: True, если успешно, иначе False.
""" """
self.logger.info(f"Обновление баланса пользователя: id={user.id}, current_balance={user.balance}, amount={amount}")
async for session in self.session_generator(): async for session in self.session_generator():
try: try:
result = await session.execute(select(User).where(User.telegram_id == telegram_id)) user = await session.get(User, user.id) # Загружаем пользователя в той же сессии
user = result.scalars().first() if not user:
if user: self.logger.warning(f"Пользователь с ID {user.id} не найден.")
user.balance += amount return False
await session.commit() # Приведение amount к Decimal
return user user.balance += Decimal(amount)
else: await session.commit()
self.logger.warning(f"Пользователь с Telegram ID {telegram_id} не найден.") self.logger.info(f"Баланс пользователя id={user.id} успешно обновлен: new_balance={user.balance}")
return None return True
except SQLAlchemyError as e: except SQLAlchemyError as e:
self.logger.error(f"Ошибка при обновлении баланса: {e}") self.logger.error(f"Ошибка при обновлении баланса пользователя id={user.id}: {e}")
await session.rollback() await session.rollback()
return None return False
async def last_subscription(self, user_id: int): async def get_last_transactions(self, user_id: UUID, limit: int = 10):
"""
Возвращает последние подписки пользователя.
"""
async for session in self.session_generator():
try:
result = await session.execute(
select(Subscription)
.where(Subscription.user_id == user_id)
.order_by(desc(Subscription.created_at))
)
return result.scalars().all()
except SQLAlchemyError as e:
self.logger.error(f"Ошибка при получении последней подписки пользователя {user_id}: {e}")
return None
async def last_transaction(self, user_id: int):
""" """
Возвращает последние транзакции пользователя. Возвращает последние транзакции пользователя.
""" """
@@ -95,8 +94,50 @@ class PostgresRepository:
select(Transaction) select(Transaction)
.where(Transaction.user_id == user_id) .where(Transaction.user_id == user_id)
.order_by(desc(Transaction.created_at)) .order_by(desc(Transaction.created_at))
.limit(limit)
) )
return result.scalars().all() return result.scalars().all()
except SQLAlchemyError as e: except SQLAlchemyError as e:
self.logger.error(f"Ошибка при получении транзакций пользователя {user_id}: {e}") self.logger.error(f"Ошибка получения транзакций пользователя {user_id}: {e}")
return None return None
async def get_last_subscription_by_user_id(self, user_id: UUID, limit: int = 1):
"""
Извлекает последнюю подписку пользователя на основании user_id.
:param user_id: UUID пользователя.
:return: Объект Subscription или None.
"""
async for session in self.session_generator():
try:
result = await session.execute(
select(Subscription)
.where(Subscription.user_id == user_id)
.order_by(desc(Subscription.created_at))
.limit(limit)
)
subscriptions = list(result.scalars())
self.logger.info(f"Найдены такие подписки: {subscriptions}")
return subscriptions
except SQLAlchemyError as e:
self.logger.error(f"Ошибка при получении подписки для пользователя {user_id}: {e}")
return None
async def add_record(self, record):
"""
Добавляет запись в базу данных.
:param record: Объект записи.
:return: Запись или None в случае ошибки.
"""
async for session in self.session_generator():
try:
session.add(record)
await session.commit()
return record
except SQLAlchemyError as e:
self.logger.error(f"Ошибка при добавлении записи: {record}: {e}")
await session.rollback()
return None

View File

@@ -69,7 +69,7 @@ class PanelInteraction:
self.logger.exception(f"Login request failed: {e}") self.logger.exception(f"Login request failed: {e}")
raise raise
async def get_inbound_info(self, inbound_id): async def get_inbound_info(self, inbound_id: int = 1):
""" """
Fetch inbound information by ID. Fetch inbound information by ID.
@@ -83,6 +83,8 @@ class PanelInteraction:
async with session.get( async with session.get(
url, headers=self.headers, ssl=self.ssl_context, timeout=10 url, headers=self.headers, ssl=self.ssl_context, timeout=10
) as response: ) as response:
response_text = await response.text() # Получаем текст ответа
self.logger.info(f"Inbound Info (raw): {response_text}") # Логируем сырой текст
if response.status == 200: if response.status == 200:
return await response.json() return await response.json()
else: else: