начало сап системы и помойму всё

This commit is contained in:
Disledg
2025-01-04 21:17:24 +03:00
parent 3544562b96
commit 9407806cc2
8 changed files with 507 additions and 18 deletions

View File

@@ -0,0 +1,65 @@
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from yookassa import Payment
from yookassa.domain.notification import WebhookNotification
from app.services.db_manager import DatabaseManager
router = APIRouter()
class CreatePaymentRequest(BaseModel):
telegram_id: int
amount: float
class PaymentResponse(BaseModel):
payment_url: str
payment_id: str
@router.post("/payment/create", response_model=PaymentResponse)
async def create_payment(request: CreatePaymentRequest):
"""
Создаёт платёж через ЮKassa и возвращает ссылку для оплаты.
"""
try:
# Создание платежа через API ЮKassa
payment = Payment.create({
"amount": {
"value": f"{request.amount:.2f}",
"currency": "RUB"
},
"confirmation": {
"type": "redirect", # Тип подтверждения (redirect или embedded)
"return_url": "https://your-app.com/success" # URL возврата
},
"description": f"Пополнение баланса для пользователя {request.telegram_id}"
})
# Возвращаем ссылку для оплаты и ID платежа
return PaymentResponse(
payment_url=payment.confirmation.confirmation_url,
payment_id=payment.id
)
except Exception as e:
raise HTTPException(status_code=500, detail=f"Ошибка создания платежа: {str(e)}")
@router.post("/payment/notification")
async def payment_notification(notification: dict, database_manager: DatabaseManager):
"""
Обрабатывает уведомления от ЮKassa.
"""
try:
# Парсим уведомление
webhook = WebhookNotification(notification)
payment_object = webhook.object
# Проверяем статус платежа
if payment_object["status"] == "succeeded":
# Обновляем баланс пользователя
telegram_id = int(payment_object["description"].split()[-1])
amount = float(payment_object["amount"]["value"])
await database_manager.update_balance(telegram_id, amount)
return {"status": "ok"}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Ошибка обработки уведомления: {str(e)}")

View File

@@ -1,3 +1,4 @@
from typing import List
from fastapi import APIRouter, HTTPException, Depends
from pydantic import BaseModel
from app.services.db_manager import DatabaseManager
@@ -53,12 +54,18 @@ async def buy_subscription(
# Эндпоинт для получения последней подписки
@router.get("/subscription/{user_id}/last", response_model=SubscriptionResponse)
async def last_subscription(user_id: UUID, database_manager: DatabaseManager = Depends(get_database_manager)):
"""
Возвращает последнюю подписку пользователя.
"""
logger.info(f"Получение последней подписки для пользователя: {user_id}")
try:
sub = await database_manager.last_subscription(user_id)
if sub is None:
subscriptions = await database_manager.last_subscriptions(user_id=str(user_id), limit=1)
if not subscriptions:
logger.warning(f"Подписки для пользователя {user_id} не найдены")
raise HTTPException(status_code=404, detail="No subscriptions found")
sub = subscriptions[0]
return {
"id": sub.id,
"plan": sub.plan,
@@ -74,4 +81,36 @@ async def last_subscription(user_id: UUID, database_manager: DatabaseManager = D
logger.error(f"Неожиданная ошибка: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.get("/subscriptions/{user_id}", response_model=List[SubscriptionResponse])
async def get_subscriptions(user_id: UUID, database_manager: DatabaseManager = Depends(get_database_manager)):
"""
Возвращает список подписок пользователя.
"""
logger.info(f"Получение подписок для пользователя: {user_id}")
try:
# Получаем подписки без ограничений или с указанным лимитом
subscriptions = await database_manager.last_subscriptions(user_id=str(user_id))
if not subscriptions:
logger.warning(f"Подписки для пользователя {user_id} не найдены")
raise HTTPException(status_code=404, detail="No subscriptions found")
# Формируем список подписок для ответа
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(),
}
for sub in subscriptions
]
except SQLAlchemyError as e:
logger.error(f"Ошибка базы данных при получении подписок для пользователя {user_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

@@ -0,0 +1,187 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.exc import SQLAlchemyError
from app.services.db_manager import DatabaseManager
from instance.configdb import get_database_manager
from uuid import UUID
from pydantic import BaseModel
from enum import Enum
from typing import List, Literal, Optional
from datetime import datetime
import logging
logger = logging.getLogger(__name__)
class TicketStatus(str, Enum):
OPEN = "open"
PENDING = "pending"
CLOSED = "closed"
class CreateTicketRequest(BaseModel):
subject: str
message: str
class TicketResponse(BaseModel):
id: int
user_id: UUID
subject: str
message: str
status: TicketStatus
created_at: datetime
updated_at: datetime
class TicketMessageRequest(BaseModel):
message: str
class TicketMessageResponse(BaseModel):
id: int
ticket_id: int
sender: str
message: str
created_at: datetime
router = APIRouter()
def handle_exception(e: Exception, message: str):
logger.error(f"{message}: {e}")
raise HTTPException(status_code=500, detail=f"{message}: {str(e)}")
@router.post("/support/tickets/{ticket_id}/messages", response_model=TicketMessageResponse, summary="Добавить сообщение")
async def add_message(
ticket_id: int,
request: TicketMessageRequest,
sender: Literal["user", "support"], # "user" или "support"
database_manager: DatabaseManager = Depends(get_database_manager)
):
"""
Добавляет сообщение в тикет.
Args:
ticket_id (int): ID тикета.
request (TicketMessageRequest): Данные сообщения.
sender (str): Отправитель ("user" или "support").
database_manager (DatabaseManager): Управление базой данных.
Returns:
TicketMessageResponse: Данные созданного сообщения.
"""
try:
message = await database_manager.add_ticket_message(ticket_id=ticket_id, sender=sender, message=request.message)
if not message:
raise HTTPException(status_code=404, detail="Тикет не найден или ошибка добавления сообщения")
return message
except Exception as e:
handle_exception(e,"Ошибка добавления сообщения")
@router.get("/support/tickets/{ticket_id}/messages", response_model=List[TicketMessageResponse], summary="Получить сообщения")
async def get_messages(
ticket_id: int,
database_manager: DatabaseManager = Depends(get_database_manager)
):
"""
Возвращает список сообщений в тикете.
Args:
ticket_id (int): ID тикета, для которого нужно получить сообщения.
database_manager (DatabaseManager): Менеджер базы данных.
Returns:
List[TicketMessageResponse]: Список сообщений, связанных с тикетом.
Raises:
HTTPException: 404, если сообщения для тикета не найдены.
HTTPException: 500, если произошла ошибка на сервере.
"""
try:
messages = await database_manager.get_ticket_messages(ticket_id=ticket_id)
if not messages:
raise HTTPException(status_code=404, detail="Сообщения для тикета не найдены")
return messages
except Exception as e:
handle_exception(e,"Ошибка получения сообщения")
@router.post("/support/tickets", response_model=TicketResponse, summary="Создать тикет")
async def create_ticket(
request: CreateTicketRequest,
user_id: UUID,
database_manager: DatabaseManager = Depends(get_database_manager)
):
"""
Создаёт новый тикет для пользователя.
Args:
request (CreateTicketRequest): Данные для создания тикета (тема и сообщение).
user_id (UUID): Идентификатор пользователя, создающего тикет.
database_manager (DatabaseManager): Менеджер базы данных.
Returns:
TicketResponse: Данные созданного тикета.
Raises:
HTTPException: 500, если произошла ошибка при создании тикета.
"""
try:
ticket = await database_manager.create_ticket(
user_id=user_id,
subject=request.subject,
message=request.message
)
return ticket
except Exception as e:
handle_exception(e,"Ошибка содания тикета")
@router.get("/support/tickets", response_model=List[TicketResponse], summary="Получить список тикетов")
async def list_tickets(
user_id: UUID,
database_manager: DatabaseManager = Depends(get_database_manager)
):
"""
Возвращает список тикетов пользователя.
Args:
user_id (UUID): Идентификатор пользователя, чьи тикеты нужно получить.
database_manager (DatabaseManager): Менеджер базы данных.
Returns:
List[TicketResponse]: Список тикетов пользователя.
Raises:
HTTPException: 404, если тикеты не найдены.
HTTPException: 500, если произошла ошибка на сервере.
"""
try:
tickets = await database_manager.list_tickets(user_id=user_id)
if not tickets:
raise HTTPException(status_code=404, detail="Тикеты не найдены")
return tickets
except Exception as e:
handle_exception(e,"Ошибка получения тикетов")
@router.patch("/support/tickets/{ticket_id}", response_model=TicketResponse, summary="Обновить статус тикета")
async def update_ticket_status(
ticket_id: int,
status: TicketStatus,
database_manager: DatabaseManager = Depends(get_database_manager)
):
"""
Обновляет статус тикета.
Args:
ticket_id (int): ID тикета, статус которого нужно обновить.
status (TicketStatus): Новый статус тикета (open, pending, closed).
database_manager (DatabaseManager): Менеджер базы данных.
Returns:
TicketResponse: Обновлённые данные тикета.
Raises:
HTTPException: 404, если тикет не найден.
HTTPException: 500, если произошла ошибка при обновлении тикета.
"""
try:
ticket = await database_manager.update_ticket_status(ticket_id=ticket_id, status=status)
if not ticket:
raise HTTPException(status_code=404, detail="Тикет не найден")
return ticket
except Exception as e:
handle_exception(e,"Ошибка обновления тикета")

View File

@@ -1,4 +1,4 @@
from instance.model import User, Subscription, Transaction, Administrators
from instance.model import User, Subscription, Transaction, Administrators, SupportTicket,TicketMessage,TicketStatus
from sqlalchemy.future import select
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy import desc
@@ -85,9 +85,11 @@ class DatabaseManager:
await session.rollback()
return "ERROR"
async def last_subscription(self, user_id: str):
async def last_subscriptions(self, user_id: str, limit: int = 10):
"""
Возвращает последнюю подписку пользователя.
Возвращает список последних подписок пользователя, ограниченный заданным количеством.
:param user_id: ID пользователя
:param limit: Максимальное количество подписок для возврата
"""
async for session in self.session_generator():
try:
@@ -95,18 +97,20 @@ class DatabaseManager:
select(Subscription)
.where(Subscription.user_id == str(user_id))
.order_by(desc(Subscription.created_at))
.limit(1) # Применяем limit правильно
.limit(limit) # Ограничиваем количество результатов
)
subscription = result.scalar_one_or_none()
if subscription:
return subscription
subscriptions = result.scalars().all() # Получаем все результаты до лимита
if subscriptions:
return subscriptions
else:
return None
self.logger.info(f"Для пользователя {user_id} подписки не найдены.")
return [] # Возвращаем пустой список, если подписок нет
except SQLAlchemyError as e:
self.logger.error(f"Ошибка при получении подписки для пользователя {user_id}: {e}")
self.logger.error(f"Ошибка при получении подписок для пользователя {user_id}: {e}")
return "ERROR"
async def last_transaction(self, user_id: UUID):
"""
Возвращает список транзакций пользователя.
@@ -228,6 +232,97 @@ class DatabaseManager:
except Exception as e:
self.logger.error(f"Ошибка проверки активной подписки для пользователя {telegram_id}: {e}")
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

View File

@@ -204,4 +204,27 @@ class PanelInteraction:
self.logger.error(f"Add client request failed: {e}")
return None
async def delete_depleted_clients(self, inbound_id=None):
"""
Удалить исчерпанных клиентов.
:param inbound_id: ID входящего соединения (inbound), если None, удаляет для всех.
:return: Ответ сервера или None в случае ошибки.
"""
await self._ensure_logged_in()
url = f"{self.base_url}/panel/api/inbounds/delDepletedClients/{inbound_id or ''}".rstrip('/')
async with aiohttp.ClientSession() as session:
try:
async with session.post(url, headers=self.headers, ssl=self.ssl_context, timeout=10) as response:
if response.status == 200:
self.logger.info(f"Depleted clients deleted successfully for inbound_id: {inbound_id}")
return await response.json()
else:
error_details = await response.text()
self.logger.error(f"Failed to delete depleted clients: {response.status} - {error_details}")
return None
except aiohttp.ClientError as e:
self.logger.error(f"Delete depleted clients request failed: {e}")
return None