add stuckt
This commit is contained in:
307
app.bot/api_client.py
Normal file
307
app.bot/api_client.py
Normal file
@@ -0,0 +1,307 @@
|
||||
"""
|
||||
HTTP клиент для взаимодействия с API
|
||||
"""
|
||||
|
||||
import aiohttp
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Optional, List, Dict, Any, Union
|
||||
from dataclasses import dataclass
|
||||
import json
|
||||
from bot_config import config
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@dataclass
|
||||
class APIResponse:
|
||||
"""Стандартный ответ API"""
|
||||
success: bool
|
||||
data: Any = None
|
||||
error: Optional[str] = None
|
||||
status_code: int = 200
|
||||
|
||||
class APIClient:
|
||||
"""HTTP клиент для работы с FastAPI"""
|
||||
|
||||
def __init__(self, base_url: str = None, timeout: int = 30):
|
||||
self.base_url = base_url or config.API_BASE_URL
|
||||
self.timeout = timeout
|
||||
self.session: Optional[aiohttp.ClientSession] = None
|
||||
|
||||
async def __aenter__(self):
|
||||
"""Асинхронный контекстный менеджер"""
|
||||
await self.create_session()
|
||||
return self
|
||||
|
||||
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||
"""Закрытие сессии"""
|
||||
await self.close_session()
|
||||
|
||||
async def create_session(self):
|
||||
"""Создание HTTP сессии"""
|
||||
if not self.session or self.session.closed:
|
||||
timeout = aiohttp.ClientTimeout(total=self.timeout)
|
||||
self.session = aiohttp.ClientSession(
|
||||
timeout=timeout,
|
||||
headers={'Content-Type': 'application/json'}
|
||||
)
|
||||
|
||||
async def close_session(self):
|
||||
"""Закрытие HTTP сессии"""
|
||||
if self.session and not self.session.closed:
|
||||
await self.session.close()
|
||||
|
||||
async def _make_request(
|
||||
self,
|
||||
method: str,
|
||||
endpoint: str,
|
||||
data: Optional[Dict] = None,
|
||||
params: Optional[Dict] = None,
|
||||
files: Optional[Dict] = None
|
||||
) -> APIResponse:
|
||||
"""Базовый метод для HTTP запросов"""
|
||||
await self.create_session()
|
||||
|
||||
url = f"{self.base_url.rstrip('/')}/{endpoint.lstrip('/')}"
|
||||
|
||||
try:
|
||||
# Если есть файлы, не устанавливаем Content-Type
|
||||
headers = {}
|
||||
if not files:
|
||||
headers['Content-Type'] = 'application/json'
|
||||
|
||||
kwargs = {
|
||||
'params': params,
|
||||
'headers': headers
|
||||
}
|
||||
|
||||
# Данные для запроса
|
||||
if files:
|
||||
# Для файлов используем FormData
|
||||
kwargs['data'] = aiohttp.FormData()
|
||||
if data:
|
||||
for key, value in data.items():
|
||||
kwargs['data'].add_field(key, str(value))
|
||||
for key, file_data in files.items():
|
||||
kwargs['data'].add_field(key, file_data)
|
||||
elif data:
|
||||
kwargs['data'] = json.dumps(data)
|
||||
|
||||
async with self.session.request(method, url, **kwargs) as response:
|
||||
status_code = response.status
|
||||
|
||||
# Обработка ответа
|
||||
if response.content_type == 'application/json':
|
||||
response_data = await response.json()
|
||||
else:
|
||||
response_data = await response.text()
|
||||
|
||||
if 200 <= status_code < 300:
|
||||
return APIResponse(
|
||||
success=True,
|
||||
data=response_data,
|
||||
status_code=status_code
|
||||
)
|
||||
else:
|
||||
error_msg = response_data.get('detail', f'HTTP {status_code}') if isinstance(response_data, dict) else str(response_data)
|
||||
logger.error(f"API Error {status_code}: {error_msg}")
|
||||
return APIResponse(
|
||||
success=False,
|
||||
error=error_msg,
|
||||
status_code=status_code
|
||||
)
|
||||
|
||||
except aiohttp.ClientError as e:
|
||||
logger.error(f"HTTP Client Error: {e}")
|
||||
return APIResponse(
|
||||
success=False,
|
||||
error=f"Connection error: {str(e)}",
|
||||
status_code=500
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Unexpected error: {e}")
|
||||
return APIResponse(
|
||||
success=False,
|
||||
error=f"Unexpected error: {str(e)}",
|
||||
status_code=500
|
||||
)
|
||||
|
||||
# ===== ПОЛЬЗОВАТЕЛИ =====
|
||||
|
||||
async def get_user_by_telegram_id(self, telegram_id: int) -> APIResponse:
|
||||
"""Получение пользователя по Telegram ID"""
|
||||
return await self._make_request('GET', f'/users/{telegram_id}')
|
||||
|
||||
async def create_user(self, user_data: Dict) -> APIResponse:
|
||||
"""Создание нового пользователя"""
|
||||
return await self._make_request('POST', '/users/', data=user_data)
|
||||
|
||||
async def update_user(self, telegram_id: int, user_data: Dict) -> APIResponse:
|
||||
"""Обновление данных пользователя"""
|
||||
return await self._make_request('PUT', f'/users/{telegram_id}', data=user_data)
|
||||
|
||||
async def get_all_users(self, limit: int = 100, offset: int = 0, is_active: Optional[bool] = None) -> APIResponse:
|
||||
"""Получение списка пользователей"""
|
||||
params = {'limit': limit, 'offset': offset}
|
||||
if is_active is not None:
|
||||
params['is_active'] = is_active
|
||||
return await self._make_request('GET', '/users/', params=params)
|
||||
|
||||
# ===== ТЕСТЫ =====
|
||||
|
||||
async def get_tests(self, is_active: Optional[bool] = None, category_id: Optional[int] = None) -> APIResponse:
|
||||
"""Получение списка тестов"""
|
||||
params = {}
|
||||
if is_active is not None:
|
||||
params['is_active'] = is_active
|
||||
if category_id is not None:
|
||||
params['category_id'] = category_id
|
||||
return await self._make_request('GET', '/tests/', params=params)
|
||||
|
||||
async def get_test_by_id(self, test_id: int) -> APIResponse:
|
||||
"""Получение теста по ID"""
|
||||
return await self._make_request('GET', f'/tests/{test_id}')
|
||||
|
||||
async def create_test(self, test_data: Dict) -> APIResponse:
|
||||
"""Создание нового теста"""
|
||||
return await self._make_request('POST', '/tests/', data=test_data)
|
||||
|
||||
# ===== ВОПРОСЫ =====
|
||||
|
||||
async def get_questions_by_test(self, test_id: int) -> APIResponse:
|
||||
"""Получение всех вопросов для теста"""
|
||||
return await self._make_request('GET', f'/questions/test/{test_id}')
|
||||
|
||||
async def get_question_by_id(self, question_id: int) -> APIResponse:
|
||||
"""Получение вопроса по ID"""
|
||||
return await self._make_request('GET', f'/questions/{question_id}')
|
||||
|
||||
async def create_question(self, question_data: Dict) -> APIResponse:
|
||||
"""Создание нового вопроса"""
|
||||
return await self._make_request('POST', '/questions/', data=question_data)
|
||||
|
||||
# ===== ВАРИАНТЫ ОТВЕТОВ =====
|
||||
|
||||
async def get_answer_options_by_question(self, question_id: int) -> APIResponse:
|
||||
"""Получение вариантов ответов для вопроса"""
|
||||
return await self._make_request('GET', f'/answer-options/question/{question_id}')
|
||||
|
||||
async def create_answer_option(self, option_data: Dict) -> APIResponse:
|
||||
"""Создание варианта ответа"""
|
||||
return await self._make_request('POST', '/answer-options/', data=option_data)
|
||||
|
||||
# ===== ИЗОБРАЖЕНИЯ =====
|
||||
|
||||
async def upload_image(self, file_content: bytes, filename: str, content_type: str, alt_text: Optional[str] = None) -> APIResponse:
|
||||
"""Загрузка изображения"""
|
||||
files = {'file': (filename, file_content, content_type)}
|
||||
data = {}
|
||||
if alt_text:
|
||||
data['alt_text'] = alt_text
|
||||
return await self._make_request('POST', '/images/upload', data=data, files=files)
|
||||
|
||||
async def get_image(self, image_id: int) -> APIResponse:
|
||||
"""Получение изображения по ID"""
|
||||
return await self._make_request('GET', f'/images/{image_id}')
|
||||
|
||||
# ===== НАСТРОЙКИ =====
|
||||
|
||||
async def get_all_settings(self) -> APIResponse:
|
||||
"""Получение всех настроек"""
|
||||
return await self._make_request('GET', '/settings/')
|
||||
|
||||
async def get_setting(self, key: str) -> APIResponse:
|
||||
"""Получение настройки по ключу"""
|
||||
return await self._make_request('GET', f'/settings/{key}')
|
||||
|
||||
async def create_setting(self, setting_data: Dict) -> APIResponse:
|
||||
"""Создание новой настройки"""
|
||||
return await self._make_request('POST', '/settings/', data=setting_data)
|
||||
|
||||
async def update_setting(self, key: str, setting_data: Dict) -> APIResponse:
|
||||
"""Обновление настройки"""
|
||||
return await self._make_request('PUT', f'/settings/{key}', data=setting_data)
|
||||
|
||||
# ===== СООБЩЕНИЯ =====
|
||||
|
||||
async def create_message(self, message_data: Dict) -> APIResponse:
|
||||
"""Создание записи о сообщении"""
|
||||
return await self._make_request('POST', '/messages/', data=message_data)
|
||||
|
||||
async def get_messages(self, user_id: Optional[int] = None, message_type: Optional[str] = None, limit: int = 100) -> APIResponse:
|
||||
"""Получение списка сообщений"""
|
||||
params = {'limit': limit}
|
||||
if user_id:
|
||||
params['user_id'] = user_id
|
||||
if message_type:
|
||||
params['message_type'] = message_type
|
||||
return await self._make_request('GET', '/messages/', params=params)
|
||||
|
||||
# ===== УТИЛИТЫ =====
|
||||
|
||||
async def health_check(self) -> bool:
|
||||
"""Проверка доступности API"""
|
||||
try:
|
||||
response = await self._make_request('GET', '/health')
|
||||
return response.success
|
||||
except Exception as e:
|
||||
logger.error(f"Health check failed: {e}")
|
||||
return False
|
||||
|
||||
# Глобальный экземпляр клиента
|
||||
api_client = APIClient()
|
||||
|
||||
# Вспомогательные функции для удобства использования
|
||||
async def ensure_user_exists(telegram_id: int, user_data: Dict) -> Optional[Dict]:
|
||||
"""Убедиться, что пользователь существует, создать если нет"""
|
||||
async with api_client:
|
||||
# Попробуем получить пользователя
|
||||
response = await api_client.get_user_by_telegram_id(telegram_id)
|
||||
|
||||
if response.success:
|
||||
return response.data
|
||||
|
||||
# Если пользователь не найден, создаем нового
|
||||
if response.status_code == 404:
|
||||
create_response = await api_client.create_user(user_data)
|
||||
if create_response.success:
|
||||
logger.info(f"Создан новый пользователь: {telegram_id}")
|
||||
return create_response.data
|
||||
else:
|
||||
logger.error(f"Ошибка создания пользователя: {create_response.error}")
|
||||
return None
|
||||
else:
|
||||
logger.error(f"Ошибка получения пользователя: {response.error}")
|
||||
return None
|
||||
|
||||
async def get_available_tests() -> List[Dict]:
|
||||
"""Получить доступные тесты"""
|
||||
async with api_client:
|
||||
response = await api_client.get_tests(is_active=True)
|
||||
if response.success:
|
||||
return response.data
|
||||
else:
|
||||
logger.error(f"Ошибка получения тестов: {response.error}")
|
||||
return []
|
||||
|
||||
async def get_test_questions_with_options(test_id: int) -> List[Dict]:
|
||||
"""Получить вопросы теста с вариантами ответов"""
|
||||
async with api_client:
|
||||
# Получаем вопросы
|
||||
questions_response = await api_client.get_questions_by_test(test_id)
|
||||
if not questions_response.success:
|
||||
logger.error(f"Ошибка получения вопросов: {questions_response.error}")
|
||||
return []
|
||||
|
||||
questions = questions_response.data
|
||||
|
||||
# Для каждого вопроса получаем варианты ответов
|
||||
for question in questions:
|
||||
options_response = await api_client.get_answer_options_by_question(question['id'])
|
||||
if options_response.success:
|
||||
question['options'] = options_response.data
|
||||
else:
|
||||
question['options'] = []
|
||||
logger.error(f"Ошибка получения вариантов для вопроса {question['id']}: {options_response.error}")
|
||||
|
||||
return questions
|
||||
Reference in New Issue
Block a user