161 lines
6.1 KiB
Python
161 lines
6.1 KiB
Python
import asyncio
|
||
import os
|
||
from importlib import import_module
|
||
from os.path import exists, join
|
||
|
||
from ariadne import load_schema_from_path, make_executable_schema
|
||
from ariadne.asgi import GraphQL
|
||
from ariadne.asgi.handlers import GraphQLHTTPHandler
|
||
from starlette.applications import Starlette
|
||
from starlette.middleware.cors import CORSMiddleware
|
||
from starlette.middleware.authentication import AuthenticationMiddleware
|
||
from starlette.middleware import Middleware
|
||
from starlette.requests import Request
|
||
from starlette.responses import FileResponse, JSONResponse, Response
|
||
from starlette.routing import Route, Mount
|
||
from starlette.staticfiles import StaticFiles
|
||
from starlette.types import ASGIApp
|
||
|
||
from cache.precache import precache_data
|
||
from cache.revalidator import revalidation_manager
|
||
from services.exception import ExceptionHandlerMiddleware
|
||
from services.redis import redis
|
||
from services.schema import create_all_tables, resolvers
|
||
from services.search import search_service
|
||
|
||
from utils.logger import root_logger as logger
|
||
from auth.internal import InternalAuthentication
|
||
from auth.middleware import AuthMiddleware
|
||
|
||
# Импортируем резолверы
|
||
import_module("resolvers")
|
||
|
||
# Создаем схему GraphQL
|
||
schema = make_executable_schema(load_schema_from_path("schema/"), resolvers)
|
||
|
||
# Пути к клиентским файлам
|
||
DIST_DIR = join(os.path.dirname(__file__), "dist") # Директория для собранных файлов
|
||
INDEX_HTML = join(os.path.dirname(__file__), "index.html")
|
||
|
||
|
||
async def index_handler(request: Request):
|
||
"""
|
||
Раздача основного HTML файла
|
||
"""
|
||
return FileResponse(INDEX_HTML)
|
||
|
||
|
||
# Создаем единый экземпляр AuthMiddleware для использования с GraphQL
|
||
auth_middleware = AuthMiddleware(lambda scope, receive, send: None)
|
||
|
||
|
||
class EnhancedGraphQLHTTPHandler(GraphQLHTTPHandler):
|
||
"""
|
||
Улучшенный GraphQL HTTP обработчик с поддержкой cookie и авторизации
|
||
"""
|
||
|
||
async def get_context_for_request(self, request: Request, data: dict) -> dict:
|
||
"""
|
||
Расширяем контекст для GraphQL запросов
|
||
"""
|
||
# Получаем стандартный контекст от базового класса
|
||
context = await super().get_context_for_request(request, data)
|
||
|
||
# Добавляем объект ответа для установки cookie
|
||
response = JSONResponse({})
|
||
context["response"] = response
|
||
|
||
# Интегрируем с AuthMiddleware
|
||
context["extensions"] = auth_middleware
|
||
|
||
return context
|
||
|
||
|
||
# Функция запуска сервера
|
||
async def start():
|
||
"""Запуск сервера и инициализация данных"""
|
||
# Создаем все таблицы в БД
|
||
create_all_tables()
|
||
|
||
# Запускаем предварительное кеширование данных
|
||
asyncio.create_task(precache_data())
|
||
|
||
# Запускаем задачу ревалидации кеша
|
||
asyncio.create_task(revalidation_manager.start())
|
||
|
||
# Выводим сообщение о запуске сервера и доступности API
|
||
logger.info("Сервер запущен и готов принимать запросы")
|
||
logger.info("GraphQL API доступно по адресу: /graphql")
|
||
logger.info("Админ-панель доступна по адресу: /admin")
|
||
|
||
|
||
# Функция остановки сервера
|
||
async def shutdown():
|
||
"""Остановка сервера и освобождение ресурсов"""
|
||
logger.info("Остановка сервера")
|
||
|
||
# Закрываем соединение с Redis
|
||
await redis.disconnect()
|
||
|
||
# Останавливаем поисковый сервис
|
||
search_service.close()
|
||
|
||
# Удаляем PID-файл, если он существует
|
||
from settings import DEV_SERVER_PID_FILE_NAME
|
||
if exists(DEV_SERVER_PID_FILE_NAME):
|
||
os.unlink(DEV_SERVER_PID_FILE_NAME)
|
||
|
||
|
||
# Создаем middleware с правильным порядком
|
||
middleware = [
|
||
# Начинаем с обработки ошибок
|
||
Middleware(ExceptionHandlerMiddleware),
|
||
# CORS должен быть перед другими middleware для корректной обработки preflight-запросов
|
||
Middleware(
|
||
CORSMiddleware,
|
||
allow_origins=["*"],
|
||
allow_methods=["GET", "POST", "OPTIONS"], # Явно указываем OPTIONS
|
||
allow_headers=["*"],
|
||
allow_credentials=True,
|
||
),
|
||
# После CORS идёт обработка авторизации
|
||
Middleware(AuthMiddleware),
|
||
# И затем аутентификация
|
||
Middleware(AuthenticationMiddleware, backend=InternalAuthentication()),
|
||
]
|
||
|
||
|
||
# Создаем экземпляр GraphQL
|
||
graphql_app = GraphQL(schema, debug=True)
|
||
|
||
|
||
# Оборачиваем GraphQL-обработчик для лучшей обработки ошибок
|
||
async def graphql_handler(request: Request):
|
||
if request.method not in ["GET", "POST", "OPTIONS"]:
|
||
return JSONResponse({"error": "Method Not Allowed by main.py"}, status_code=405)
|
||
|
||
try:
|
||
result = await graphql_app.handle_request(request)
|
||
if isinstance(result, Response):
|
||
return result
|
||
return JSONResponse(result)
|
||
except asyncio.CancelledError:
|
||
return JSONResponse({"error": "Request cancelled"}, status_code=499)
|
||
except Exception as e:
|
||
print(f"GraphQL error: {str(e)}")
|
||
return JSONResponse({"error": str(e)}, status_code=500)
|
||
|
||
# Добавляем маршруты, порядок имеет значение
|
||
routes = [
|
||
Route("/graphql", graphql_handler, methods=["GET", "POST", "OPTIONS"]),
|
||
Mount("/", app=StaticFiles(directory=DIST_DIR, html=True)),
|
||
]
|
||
|
||
# Создаем приложение Starlette с маршрутами и middleware
|
||
app = Starlette(
|
||
routes=routes,
|
||
middleware=middleware,
|
||
on_startup=[start],
|
||
on_shutdown=[shutdown],
|
||
)
|