- Объединены три проекта в один репозиторий - LichessWebServices - REST API для статистики - LichessClientTG_bot - Telegram бот с поддержкой множества пользователей - LichessWebView - Веб-интерфейс для просмотра пользователей и игроков - Добавлен общий docker-compose.yml для запуска всех сервисов - Добавлен скрипт start.sh для удобного запуска - Добавлен README с полным описанием проекта
705 lines
34 KiB
Python
705 lines
34 KiB
Python
"""
|
||
Lichess Statistics API - Основной модуль FastAPI приложения
|
||
|
||
Этот модуль содержит все API эндпоинты для получения статистики игроков Lichess.
|
||
Включает в себя:
|
||
- Статистику игр за разные периоды (сегодня, вчера, неделя)
|
||
- Детальную статистику игр за произвольный период
|
||
- Статистику решения задач (пазлов) за период
|
||
- Health check и информационные эндпоинты
|
||
|
||
Автор: Lichess Web Services Team
|
||
Версия: 1.0.0
|
||
"""
|
||
|
||
from fastapi import FastAPI, HTTPException, Path, Query, Header
|
||
from fastapi.middleware.cors import CORSMiddleware
|
||
from contextlib import asynccontextmanager
|
||
import logging
|
||
from datetime import datetime
|
||
from stats_service import StatsService
|
||
from models import ActivityResponse, ErrorResponse, HealthResponse, GamesOfPeriodResponse, PuzzleOfPeriodResponse
|
||
|
||
# =============================================================================
|
||
# НАСТРОЙКА ЛОГИРОВАНИЯ
|
||
# =============================================================================
|
||
# Настройка базового логирования для всего приложения
|
||
# Уровень INFO позволяет видеть все важные события и ошибки
|
||
logging.basicConfig(level=logging.INFO)
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# =============================================================================
|
||
# ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ
|
||
# =============================================================================
|
||
# Глобальный экземпляр сервиса статистики
|
||
# Инициализируется при запуске приложения и используется во всех эндпоинтах
|
||
stats_service = None
|
||
|
||
# =============================================================================
|
||
# УПРАВЛЕНИЕ ЖИЗНЕННЫМ ЦИКЛОМ ПРИЛОЖЕНИЯ
|
||
# =============================================================================
|
||
@asynccontextmanager
|
||
async def lifespan(app: FastAPI):
|
||
"""
|
||
Контекстный менеджер для управления жизненным циклом FastAPI приложения.
|
||
|
||
Выполняется при запуске и остановке приложения:
|
||
- При запуске: инициализирует сервис статистики
|
||
- При остановке: корректно закрывает все соединения
|
||
|
||
Args:
|
||
app: Экземпляр FastAPI приложения
|
||
"""
|
||
# ========== STARTUP (Запуск приложения) ==========
|
||
global stats_service
|
||
# Создаем экземпляр сервиса статистики, который будет использоваться во всех эндпоинтах
|
||
stats_service = StatsService()
|
||
logger.info("Lichess API сервис запущен")
|
||
|
||
# Передаем управление приложению
|
||
yield
|
||
|
||
# ========== SHUTDOWN (Остановка приложения) ==========
|
||
# Корректно закрываем все соединения и освобождаем ресурсы
|
||
if stats_service:
|
||
await stats_service.close()
|
||
logger.info("Lichess API сервис остановлен")
|
||
|
||
# =============================================================================
|
||
# СОЗДАНИЕ FASTAPI ПРИЛОЖЕНИЯ
|
||
# =============================================================================
|
||
app = FastAPI(
|
||
title="Lichess Statistics API",
|
||
description="""
|
||
## Lichess Statistics API
|
||
|
||
REST API для получения детальной статистики игроков платформы Lichess.
|
||
|
||
### Возможности:
|
||
* 📊 Получение статистики игр по режимам (Bullet, Blitz, Rapid)
|
||
* 🧩 Статистика решения задач (пазлов)
|
||
* 📅 Статистика за разные периоды (сегодня, вчера, неделя)
|
||
* 🎯 Отслеживание изменений рейтинга
|
||
* 📈 Подробная аналитика результатов игр
|
||
|
||
### Режимы игр:
|
||
- **Bullet**: Быстрые игры (1-3 минуты)
|
||
- **Blitz**: Блиц игры (3-10 минут)
|
||
- **Rapid**: Рапид игры (10+ минут)
|
||
|
||
### Примеры использования:
|
||
- Получить статистику за сегодня: `GET /stats/{username}/today`
|
||
- Получить статистику за вчера: `GET /stats/{username}/yesterday`
|
||
- Получить статистику за неделю: `GET /stats/{username}/week`
|
||
""",
|
||
version="1.0.0",
|
||
contact={
|
||
"name": "Lichess Statistics API Support",
|
||
"url": "https://github.com/vrubelroman/LichessWebServices",
|
||
},
|
||
license_info={
|
||
"name": "MIT",
|
||
},
|
||
lifespan=lifespan,
|
||
openapi_tags=[
|
||
{
|
||
"name": "health",
|
||
"description": "Проверка состояния сервиса"
|
||
},
|
||
{
|
||
"name": "statistics",
|
||
"description": "Получение статистики игроков Lichess"
|
||
},
|
||
{
|
||
"name": "info",
|
||
"description": "Информация об API"
|
||
}
|
||
]
|
||
)
|
||
|
||
# =============================================================================
|
||
# НАСТРОЙКА CORS (Cross-Origin Resource Sharing)
|
||
# =============================================================================
|
||
# CORS middleware позволяет веб-приложениям делать запросы к API с других доменов
|
||
app.add_middleware(
|
||
CORSMiddleware,
|
||
allow_origins=["*"], # В продакшене следует ограничить домены для безопасности
|
||
allow_credentials=True, # Разрешаем отправку cookies и авторизационных заголовков
|
||
allow_methods=["*"], # Разрешаем все HTTP методы (GET, POST, PUT, DELETE и т.д.)
|
||
allow_headers=["*"], # Разрешаем все заголовки
|
||
)
|
||
|
||
# =============================================================================
|
||
# API ЭНДПОИНТЫ
|
||
# =============================================================================
|
||
|
||
@app.get("/", tags=["info"])
|
||
async def root():
|
||
"""
|
||
## Корневой endpoint
|
||
|
||
Возвращает основную информацию об API и доступных эндпоинтах.
|
||
Используется для получения базовой информации о сервисе.
|
||
|
||
### Возвращает:
|
||
- Название API
|
||
- Версию
|
||
- Список доступных эндпоинтов
|
||
- Ссылки на документацию
|
||
"""
|
||
return {
|
||
"message": "Lichess Statistics API",
|
||
"version": "1.0.0",
|
||
"description": "REST API для получения статистики игроков Lichess",
|
||
"endpoints": {
|
||
"today": "/stats/{username}/today", # Статистика за сегодня
|
||
"yesterday": "/stats/{username}/yesterday", # Статистика за вчера
|
||
"week": "/stats/{username}/week", # Статистика за неделю
|
||
"games_period": "/games/{username}/period?since={timestamp}&until={timestamp}", # Статистика игр за период
|
||
"puzzle_period": "/puzzle/period?since={timestamp}&until={timestamp}&max={max}" # Статистика задач за период
|
||
},
|
||
"documentation": "/docs", # Swagger UI документация
|
||
"openapi_schema": "/openapi.json" # OpenAPI схема
|
||
}
|
||
|
||
@app.get("/health",
|
||
response_model=HealthResponse,
|
||
tags=["health"],
|
||
summary="Health Check",
|
||
description="Проверка состояния сервиса")
|
||
async def health_check():
|
||
"""
|
||
## Проверка здоровья сервиса
|
||
|
||
Простой endpoint для проверки работоспособности API.
|
||
Используется для мониторинга и health checks в production среде.
|
||
|
||
### Возвращает:
|
||
- Статус сервиса (healthy/unhealthy)
|
||
- Время проверки в ISO формате
|
||
- Название сервиса
|
||
|
||
### Использование:
|
||
- Мониторинг системы
|
||
- Load balancer health checks
|
||
- Kubernetes liveness/readiness probes
|
||
"""
|
||
return HealthResponse(
|
||
status="healthy", # Всегда возвращает healthy, если сервис запущен
|
||
timestamp=datetime.now().isoformat(), # Текущее время в ISO формате
|
||
service="Lichess Statistics API"
|
||
)
|
||
|
||
@app.get("/stats/{username}/today",
|
||
response_model=ActivityResponse,
|
||
tags=["statistics"],
|
||
summary="Статистика за сегодня",
|
||
description="Получает детальную статистику игрока за сегодняшний день",
|
||
responses={
|
||
200: {
|
||
"description": "Статистика успешно получена",
|
||
"content": {
|
||
"application/json": {
|
||
"example": {
|
||
"message": "Статистика за сегодняшний день",
|
||
"data": {
|
||
"username": "magnus",
|
||
"tasks": {
|
||
"total": 15,
|
||
"solved": 12,
|
||
"unsolved": 3
|
||
},
|
||
"games": {
|
||
"bullet": {
|
||
"games_played": 8,
|
||
"rating_change": 15,
|
||
"final_rating": 2850,
|
||
"wins": 5,
|
||
"losses": 2,
|
||
"draws": 1
|
||
},
|
||
"blitz": {
|
||
"games_played": 3,
|
||
"rating_change": -5,
|
||
"final_rating": 2750,
|
||
"wins": 1,
|
||
"losses": 2,
|
||
"draws": 0
|
||
},
|
||
"rapid": {
|
||
"games_played": 0,
|
||
"rating_change": 0,
|
||
"final_rating": 0,
|
||
"wins": 0,
|
||
"losses": 0,
|
||
"draws": 0
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
404: {
|
||
"description": "Пользователь не найден",
|
||
"content": {
|
||
"application/json": {
|
||
"example": {
|
||
"message": "Пользователь magnus не найден или неактивен"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
500: {
|
||
"description": "Внутренняя ошибка сервера",
|
||
"content": {
|
||
"application/json": {
|
||
"example": {
|
||
"detail": "Внутренняя ошибка сервера: Connection timeout"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
})
|
||
async def get_today_stats(
|
||
username: str = Path(...,
|
||
description="Имя пользователя на Lichess",
|
||
example="magnus",
|
||
min_length=1,
|
||
max_length=50)
|
||
):
|
||
"""
|
||
## Статистика за сегодняшний день
|
||
|
||
Получает детальную статистику игрока за сегодняшний день, включая:
|
||
|
||
### Статистика игр:
|
||
- **Bullet**: Быстрые игры (1-3 минуты)
|
||
- **Blitz**: Блиц игры (3-10 минут)
|
||
- **Rapid**: Рапид игры (10+ минут)
|
||
|
||
### Для каждого режима:
|
||
- Количество сыгранных игр
|
||
- Изменение рейтинга
|
||
- Текущий рейтинг
|
||
- Количество побед, поражений, ничьих
|
||
|
||
### Статистика задач:
|
||
- Общее количество решенных задач
|
||
- Количество решенных задач
|
||
- Количество нерешенных задач
|
||
|
||
### Параметры:
|
||
- **username**: Имя пользователя на Lichess (обязательно)
|
||
|
||
### Возможные ошибки:
|
||
- **404**: Пользователь не найден или неактивен
|
||
- **500**: Внутренняя ошибка сервера
|
||
"""
|
||
# Проверяем, что сервис статистики инициализирован
|
||
if not stats_service:
|
||
raise HTTPException(status_code=500, detail="Сервис не инициализирован")
|
||
|
||
try:
|
||
# Получаем статистику за сегодняшний день через сервис
|
||
result = await stats_service.get_today_stats(username)
|
||
return result
|
||
except Exception as e:
|
||
# Логируем ошибку и возвращаем HTTP 500
|
||
logger.error(f"Ошибка в endpoint get_today_stats: {e}")
|
||
raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}")
|
||
|
||
@app.get("/stats/{username}/yesterday",
|
||
response_model=ActivityResponse,
|
||
tags=["statistics"],
|
||
summary="Статистика за вчера",
|
||
description="Получает детальную статистику игрока за вчерашний день")
|
||
async def get_yesterday_stats(
|
||
username: str = Path(...,
|
||
description="Имя пользователя на Lichess",
|
||
example="magnus",
|
||
min_length=1,
|
||
max_length=50)
|
||
):
|
||
"""
|
||
## Статистика за вчерашний день
|
||
|
||
Получает детальную статистику игрока за вчерашний день.
|
||
|
||
### Возвращает:
|
||
- Статистику игр по всем режимам (Bullet, Blitz, Rapid)
|
||
- Статистику решения задач (пазлов)
|
||
- Изменения рейтинга
|
||
- Результаты игр (победы, поражения, ничьи)
|
||
|
||
### Параметры:
|
||
- **username**: Имя пользователя на Lichess (обязательно)
|
||
"""
|
||
if not stats_service:
|
||
raise HTTPException(status_code=500, detail="Сервис не инициализирован")
|
||
|
||
try:
|
||
result = await stats_service.get_yesterday_stats(username)
|
||
return result
|
||
except Exception as e:
|
||
logger.error(f"Ошибка в endpoint get_yesterday_stats: {e}")
|
||
raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}")
|
||
|
||
@app.get("/stats/{username}/week",
|
||
response_model=ActivityResponse,
|
||
tags=["statistics"],
|
||
summary="Статистика за неделю",
|
||
description="Получает агрегированную статистику игрока за последние 7 дней")
|
||
async def get_week_stats(
|
||
username: str = Path(...,
|
||
description="Имя пользователя на Lichess",
|
||
example="magnus",
|
||
min_length=1,
|
||
max_length=50)
|
||
):
|
||
"""
|
||
## Статистика за последние 7 дней
|
||
|
||
Получает агрегированную статистику игрока за последние 7 дней.
|
||
|
||
### Особенности:
|
||
- Суммирует все игры и задачи за неделю
|
||
- Показывает общее изменение рейтинга
|
||
- Отображает финальный рейтинг на конец периода
|
||
|
||
### Возвращает:
|
||
- Общую статистику игр по всем режимам
|
||
- Суммарную статистику решения задач
|
||
- Агрегированные изменения рейтинга
|
||
|
||
### Параметры:
|
||
- **username**: Имя пользователя на Lichess (обязательно)
|
||
"""
|
||
if not stats_service:
|
||
raise HTTPException(status_code=500, detail="Сервис не инициализирован")
|
||
|
||
try:
|
||
result = await stats_service.get_week_stats(username)
|
||
return result
|
||
except Exception as e:
|
||
logger.error(f"Ошибка в endpoint get_week_stats: {e}")
|
||
raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}")
|
||
|
||
@app.get("/games/{username}/period",
|
||
response_model=GamesOfPeriodResponse,
|
||
tags=["statistics"],
|
||
summary="Статистика игр за период",
|
||
description="Получает детальную статистику игр пользователя за указанный период",
|
||
responses={
|
||
200: {
|
||
"description": "Статистика игр успешно получена",
|
||
"content": {
|
||
"application/json": {
|
||
"example": {
|
||
"message": "Статистика игр за период",
|
||
"username": "magnus",
|
||
"period_start": 1640995200000,
|
||
"period_end": 1641081600000,
|
||
"games_count": 25,
|
||
"data": {
|
||
"bullet": {
|
||
"games_played": 10,
|
||
"wins": 6,
|
||
"losses": 3,
|
||
"draws": 1,
|
||
"rating_change": 15,
|
||
"rating": 2850
|
||
},
|
||
"blitz": {
|
||
"games_played": 8,
|
||
"wins": 5,
|
||
"losses": 2,
|
||
"draws": 1,
|
||
"rating_change": 12,
|
||
"rating": 2750
|
||
},
|
||
"rapid": {
|
||
"games_played": 5,
|
||
"wins": 3,
|
||
"losses": 1,
|
||
"draws": 1,
|
||
"rating_change": 8,
|
||
"rating": 2600
|
||
},
|
||
"classical": {
|
||
"games_played": 2,
|
||
"wins": 1,
|
||
"losses": 1,
|
||
"draws": 0,
|
||
"rating_change": 0,
|
||
"rating": 2400
|
||
},
|
||
"correspondence": {
|
||
"games_played": 0,
|
||
"wins": 0,
|
||
"losses": 0,
|
||
"draws": 0,
|
||
"rating_change": 0,
|
||
"rating": None
|
||
},
|
||
"total": {
|
||
"games_played": 25,
|
||
"wins": 15,
|
||
"losses": 7,
|
||
"draws": 3,
|
||
"rating_change": 35,
|
||
"rating": 2850
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
400: {
|
||
"description": "Некорректные параметры запроса",
|
||
"content": {
|
||
"application/json": {
|
||
"example": {
|
||
"detail": "Параметр 'since' должен быть меньше 'until'"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
404: {
|
||
"description": "Пользователь не найден",
|
||
"content": {
|
||
"application/json": {
|
||
"example": {
|
||
"message": "Пользователь magnus не найден"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
500: {
|
||
"description": "Внутренняя ошибка сервера",
|
||
"content": {
|
||
"application/json": {
|
||
"example": {
|
||
"detail": "Внутренняя ошибка сервера: Connection timeout"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
})
|
||
async def get_games_of_period(
|
||
username: str = Path(...,
|
||
description="Имя пользователя на Lichess",
|
||
example="magnus",
|
||
min_length=1,
|
||
max_length=50),
|
||
since: int = Query(...,
|
||
description="Начало периода (Unix timestamp в миллисекундах)",
|
||
example=1640995200000),
|
||
until: int = Query(...,
|
||
description="Конец периода (Unix timestamp в миллисекундах)",
|
||
example=1641081600000),
|
||
rated_only: bool = Query(True,
|
||
description="Только рейтинговые игры (по умолчанию true - рекомендуется)",
|
||
example=True)
|
||
):
|
||
"""
|
||
## Статистика игр за период
|
||
|
||
Получает детальную статистику игр пользователя за указанный период времени.
|
||
Этот эндпоинт позволяет получить подробную аналитику по играм за любой период.
|
||
|
||
### Возможности:
|
||
- **Фильтрация по времени**: точный период с Unix timestamp
|
||
- **Типы игр**: Bullet, Blitz, Rapid, Classical, Correspondence
|
||
- **Статистика результатов**: победы, поражения, ничьи
|
||
- **Рейтинговые изменения**: суммарные изменения рейтинга
|
||
- **Итоговый рейтинг**: рейтинг после последней игры в каждом режиме
|
||
- **Фильтр рейтинговых игр**: только рейтинговые или все игры
|
||
|
||
### Параметры:
|
||
- **username**: Имя пользователя на Lichess (обязательно)
|
||
- **since**: Начало периода в Unix timestamp (миллисекунды) (обязательно)
|
||
- **until**: Конец периода в Unix timestamp (миллисекунды) (обязательно)
|
||
- **rated_only**: Только рейтинговые игры (по умолчанию true - рекомендуется)
|
||
|
||
### Примеры использования:
|
||
- Статистика за последние 7 дней: `since=1640995200000&until=1641081600000` (по умолчанию только рейтинговые)
|
||
- Только рейтинговые игры: `rated_only=true` (рекомендуется)
|
||
- Все игры: `rated_only=false`
|
||
|
||
### Возможные ошибки:
|
||
- **400**: Некорректные параметры (since >= until)
|
||
- **404**: Пользователь не найден
|
||
- **500**: Внутренняя ошибка сервера
|
||
"""
|
||
# Проверяем, что сервис статистики инициализирован
|
||
if not stats_service:
|
||
raise HTTPException(status_code=500, detail="Сервис не инициализирован")
|
||
|
||
# Валидация параметров времени
|
||
if since >= until:
|
||
raise HTTPException(
|
||
status_code=400,
|
||
detail="Параметр 'since' должен быть меньше 'until'"
|
||
)
|
||
|
||
# Проверяем разумность периода (не более 1 года в миллисекундах)
|
||
if until - since > 365 * 24 * 3600 * 1000:
|
||
raise HTTPException(
|
||
status_code=400,
|
||
detail="Период не может превышать 1 год"
|
||
)
|
||
|
||
try:
|
||
# Конвертируем миллисекунды в секунды для внутренней логики
|
||
since_seconds = since // 1000
|
||
until_seconds = until // 1000
|
||
result = await stats_service.get_games_of_period(username, since_seconds, until_seconds, rated_only)
|
||
return result
|
||
except Exception as e:
|
||
logger.error(f"Ошибка в endpoint get_games_of_period: {e}")
|
||
raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}")
|
||
|
||
@app.get("/puzzle/period",
|
||
response_model=PuzzleOfPeriodResponse,
|
||
tags=["statistics"],
|
||
summary="Статистика решения задач за период",
|
||
description="Получает статистику решения задач (пазлов) за указанный период времени. Требует авторизации через Bearer токен.",
|
||
responses={
|
||
200: {
|
||
"description": "Статистика решения задач успешно получена",
|
||
"content": {
|
||
"application/json": {
|
||
"example": {
|
||
"message": "Статистика решения задач за период",
|
||
"period_start": 1640995200000,
|
||
"period_end": 1641081600000,
|
||
"max_puzzles": 50,
|
||
"puzzles_in_period": 15,
|
||
"data": {
|
||
"total_attempts": 15,
|
||
"solved": 12,
|
||
"failed": 3,
|
||
"success_rate": 80.0
|
||
}
|
||
}
|
||
}
|
||
}
|
||
},
|
||
400: {
|
||
"description": "Некорректные параметры запроса",
|
||
"content": {
|
||
"application/json": {
|
||
"example": {
|
||
"detail": "Параметр 'since' должен быть меньше 'until'"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
401: {
|
||
"description": "Неверный токен авторизации",
|
||
"content": {
|
||
"application/json": {
|
||
"example": {
|
||
"message": "Неверный токен авторизации или доступ запрещен"
|
||
}
|
||
}
|
||
}
|
||
},
|
||
500: {
|
||
"description": "Внутренняя ошибка сервера",
|
||
"content": {
|
||
"application/json": {
|
||
"example": {
|
||
"detail": "Внутренняя ошибка сервера: Connection timeout"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
})
|
||
async def get_puzzle_of_period(
|
||
since: int = Query(...,
|
||
description="Начало периода (Unix timestamp в миллисекундах)",
|
||
example=1640995200000),
|
||
until: int = Query(...,
|
||
description="Конец периода (Unix timestamp в миллисекундах)",
|
||
example=1641081600000),
|
||
max: int = Query(50,
|
||
description="Максимальное количество задач для получения от Lichess API. Внимание: если в периоде было больше задач, чем указано в max, будут показаны только последние N активностей",
|
||
example=50,
|
||
ge=1,
|
||
le=1000),
|
||
authorization: str = Header(...,
|
||
description="Bearer токен авторизации",
|
||
example="Bearer your_token_here")
|
||
):
|
||
"""
|
||
## Статистика решения задач за период
|
||
|
||
Получает статистику решения задач (пазлов) за указанный период времени.
|
||
Требует авторизации через Bearer токен от Lichess.
|
||
|
||
### Возможности:
|
||
- **Фильтрация по времени**: точный период с Unix timestamp в миллисекундах
|
||
- **Статистика решений**: количество решенных и нерешенных задач
|
||
- **Процент успеха**: автоматический расчет процента успешных решений
|
||
- **Настраиваемый лимит**: максимальное количество задач для анализа
|
||
|
||
### Параметры:
|
||
- **since**: Начало периода в Unix timestamp (миллисекунды) (обязательно)
|
||
- **until**: Конец периода в Unix timestamp (миллисекунды) (обязательно)
|
||
- **max**: Максимальное количество задач (по умолчанию 50, максимум 1000)
|
||
- **Authorization**: Bearer токен в заголовке (обязательно)
|
||
|
||
### Примеры использования:
|
||
- Статистика за последние 7 дней: `since=1640995200000&until=1641081600000`
|
||
- Больше задач: `max=100`
|
||
- Заголовок: `Authorization: Bearer your_token_here`
|
||
|
||
### Получение токена:
|
||
1. Зайдите на https://lichess.org/account/oauth/token/create
|
||
2. Создайте новый токен с правами на чтение активности
|
||
3. Используйте токен в заголовке Authorization
|
||
|
||
### Возможные ошибки:
|
||
- **400**: Некорректные параметры (since >= until, неверный max)
|
||
- **401**: Неверный токен авторизации
|
||
- **500**: Внутренняя ошибка сервера
|
||
"""
|
||
# Проверяем, что сервис статистики инициализирован
|
||
if not stats_service:
|
||
raise HTTPException(status_code=500, detail="Сервис не инициализирован")
|
||
|
||
# Валидация параметров
|
||
if since >= until:
|
||
raise HTTPException(
|
||
status_code=400,
|
||
detail="Параметр 'since' должен быть меньше 'until'"
|
||
)
|
||
|
||
# Проверяем разумность периода (не более 1 года в миллисекундах)
|
||
if until - since > 365 * 24 * 3600 * 1000:
|
||
raise HTTPException(
|
||
status_code=400,
|
||
detail="Период не может превышать 1 год"
|
||
)
|
||
|
||
# Извлекаем токен из заголовка Authorization
|
||
if not authorization.startswith("Bearer "):
|
||
raise HTTPException(
|
||
status_code=401,
|
||
detail="Неверный формат токена. Используйте 'Bearer your_token'"
|
||
)
|
||
|
||
token = authorization[7:] # Убираем "Bearer " из начала
|
||
|
||
try:
|
||
result = await stats_service.get_puzzle_of_period(token, since, until, max)
|
||
return result
|
||
except Exception as e:
|
||
logger.error(f"Ошибка в endpoint get_puzzle_of_period: {e}")
|
||
raise HTTPException(status_code=500, detail=f"Внутренняя ошибка сервера: {str(e)}")
|
||
|
||
if __name__ == "__main__":
|
||
import uvicorn
|
||
uvicorn.run(app, host="0.0.0.0", port=8000)
|