core/main.py
2025-05-19 11:25:41 +03:00

161 lines
6.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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],
)