uploader-bot/app/fastapi_main.py

408 lines
14 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.

"""
Главное FastAPI приложение - полная миграция от Sanic
Интеграция всех модулей: middleware, routes, системы
"""
import asyncio
import logging
import time
from contextlib import asynccontextmanager
from typing import Dict, Any
from fastapi import FastAPI, Request, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.trustedhost import TrustedHostMiddleware
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError
import uvicorn
# Импорт компонентов приложения
from app.core.config import get_settings
from app.core.database import db_manager, get_cache_manager
from app.core.logging import configure_logging, get_logger
from app.core.crypto import get_ed25519_manager
# Импорт middleware
from app.api.fastapi_middleware import (
FastAPISecurityMiddleware,
FastAPIRateLimitMiddleware,
FastAPICryptographicMiddleware,
FastAPIRequestContextMiddleware,
FastAPIAuthenticationMiddleware
)
# Импорт роутеров
from app.api.fastapi_auth_routes import router as auth_router
from app.api.fastapi_content_routes import router as content_router
from app.api.fastapi_storage_routes import router as storage_router
from app.api.fastapi_node_routes import router as node_router
from app.api.fastapi_system_routes import router as system_router
# Глобальные переменные для мониторинга
_app_start_time = time.time()
@asynccontextmanager
async def lifespan(app: FastAPI):
"""
Управление жизненным циклом приложения
Startup и shutdown события
"""
# Startup
logger = get_logger(__name__)
settings = get_settings()
try:
await logger.ainfo("=== FastAPI Application Starting ===")
# === DEBUG: PostgreSQL DRIVERS VALIDATION ===
await logger.ainfo("=== DEBUGGING psycopg2 ERROR ===")
# Проверка psycopg2
try:
import psycopg2
await logger.ainfo("✅ psycopg2 импортирован успешно", version=psycopg2.__version__)
except ImportError as e:
await logger.aerror("❌ ОШИБКА: psycopg2 не найден", error=str(e))
except Exception as e:
await logger.aerror("❌ ОШИБКА: psycopg2 другая ошибка", error=str(e))
# Проверка asyncpg
try:
import asyncpg
await logger.ainfo("✅ asyncpg импортирован успешно", version=asyncpg.__version__)
except ImportError as e:
await logger.aerror("❌ ОШИБКА: asyncpg не найден", error=str(e))
except Exception as e:
await logger.aerror("❌ ОШИБКА: asyncpg другая ошибка", error=str(e))
# Проверка SQLAlchemy драйверов
try:
from sqlalchemy.dialects import postgresql
await logger.ainfo("✅ SQLAlchemy PostgreSQL диалект доступен")
except ImportError as e:
await logger.aerror("❌ ОШИБКА: SQLAlchemy PostgreSQL диалект недоступен", error=str(e))
# Проверка DATABASE_URL
from app.core.config import DATABASE_URL
await logger.ainfo("🔧 DATABASE_URL конфигурация", url=DATABASE_URL)
await logger.ainfo("=== END DEBUGGING ===")
# Инициализация базы данных
await logger.ainfo("Initializing database connection...")
await db_manager.initialize()
# Инициализация кэша
await logger.ainfo("Initializing cache manager...")
cache_manager = await get_cache_manager()
await cache_manager.initialize() if hasattr(cache_manager, 'initialize') else None
# Инициализация криптографии
await logger.ainfo("Initializing cryptographic manager...")
crypto_manager = get_ed25519_manager()
await logger.ainfo(f"Node ID: {crypto_manager.node_id}")
# Проверка готовности системы
await logger.ainfo("System initialization completed successfully")
yield
except Exception as e:
await logger.aerror(f"Failed to initialize application: {e}")
raise
finally:
# Shutdown
await logger.ainfo("=== FastAPI Application Shutting Down ===")
# Закрытие соединений с базой данных
try:
await db_manager.close()
await logger.ainfo("Database connections closed")
except Exception as e:
await logger.aerror(f"Error closing database: {e}")
# Закрытие кэша
try:
cache_manager = await get_cache_manager()
if hasattr(cache_manager, 'close'):
await cache_manager.close()
await logger.ainfo("Cache connections closed")
except Exception as e:
await logger.aerror(f"Error closing cache: {e}")
await logger.ainfo("Application shutdown completed")
def create_fastapi_app() -> FastAPI:
"""
Создание и конфигурация FastAPI приложения
"""
settings = get_settings()
# Создание приложения
app = FastAPI(
title="MY Network Uploader Bot - FastAPI",
description="Decentralized content uploader with web2-client compatibility",
version="3.0.0",
docs_url="/docs" if getattr(settings, 'DEBUG', False) else None,
redoc_url="/redoc" if getattr(settings, 'DEBUG', False) else None,
lifespan=lifespan
)
# Настройка CORS
app.add_middleware(
CORSMiddleware,
allow_origins=getattr(settings, 'ALLOWED_ORIGINS', ["*"]),
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["*"],
)
# Безопасность хостов
trusted_hosts = getattr(settings, 'TRUSTED_HOSTS', ["*"])
if trusted_hosts != ["*"]:
app.add_middleware(TrustedHostMiddleware, allowed_hosts=trusted_hosts)
# Кастомные middleware (в правильном порядке)
app.add_middleware(FastAPIRequestContextMiddleware)
app.add_middleware(FastAPIAuthenticationMiddleware)
app.add_middleware(FastAPICryptographicMiddleware)
app.add_middleware(FastAPIRateLimitMiddleware)
app.add_middleware(FastAPISecurityMiddleware)
# Регистрация роутеров
app.include_router(auth_router)
app.include_router(content_router)
app.include_router(storage_router, prefix="/api/storage")
app.include_router(node_router)
app.include_router(system_router)
# Дополнительные обработчики событий
setup_exception_handlers(app)
setup_middleware_hooks(app)
return app
def setup_exception_handlers(app: FastAPI):
"""
Настройка обработчиков исключений
"""
logger = get_logger(__name__)
@app.exception_handler(HTTPException)
async def http_exception_handler(request: Request, exc: HTTPException):
"""Обработка HTTP исключений"""
await logger.awarning(
f"HTTP Exception: {exc.status_code}",
path=str(request.url),
method=request.method,
detail=exc.detail
)
return JSONResponse(
status_code=exc.status_code,
content={
"error": exc.detail,
"status_code": exc.status_code,
"timestamp": time.time()
}
)
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
"""Обработка ошибок валидации"""
await logger.awarning(
"Validation Error",
path=str(request.url),
method=request.method,
errors=exc.errors()
)
return JSONResponse(
status_code=422,
content={
"error": "Validation failed",
"details": exc.errors(),
"status_code": 422,
"timestamp": time.time()
}
)
@app.exception_handler(Exception)
async def general_exception_handler(request: Request, exc: Exception):
"""Обработка общих исключений"""
await logger.aerror(
f"Unhandled exception: {type(exc).__name__}",
path=str(request.url),
method=request.method,
error=str(exc)
)
# Увеличиваем счетчик ошибок для мониторинга
from app.api.fastapi_system_routes import increment_error_counter
await increment_error_counter()
return JSONResponse(
status_code=500,
content={
"error": "Internal server error",
"status_code": 500,
"timestamp": time.time()
}
)
def setup_middleware_hooks(app: FastAPI):
"""
Настройка хуков middleware для мониторинга
"""
@app.middleware("http")
async def monitoring_middleware(request: Request, call_next):
"""Middleware для мониторинга запросов"""
start_time = time.time()
# Увеличиваем счетчик запросов
from app.api.fastapi_system_routes import increment_request_counter
await increment_request_counter()
# Проверяем режим обслуживания
try:
cache_manager = await get_cache_manager()
maintenance_mode = await cache_manager.get("maintenance_mode")
if maintenance_mode and request.url.path not in ["/api/system/health", "/api/system/live"]:
return JSONResponse(
status_code=503,
content={
"error": "Service temporarily unavailable",
"message": maintenance_mode.get("message", "System is under maintenance"),
"status_code": 503
}
)
except Exception:
pass # Продолжаем работу если кэш недоступен
# Выполняем запрос
try:
response = await call_next(request)
process_time = time.time() - start_time
# Добавляем заголовки мониторинга
response.headers["X-Process-Time"] = str(process_time)
response.headers["X-Request-ID"] = getattr(request.state, 'request_id', 'unknown')
return response
except Exception as e:
# Логируем ошибку и увеличиваем счетчик
logger = get_logger(__name__)
await logger.aerror(
f"Request processing failed: {e}",
path=str(request.url),
method=request.method
)
from app.api.fastapi_system_routes import increment_error_counter
await increment_error_counter()
raise
# Создание экземпляра приложения
app = create_fastapi_app()
# Дополнительные корневые эндпоинты для совместимости
@app.get("/")
async def root():
"""Корневой эндпоинт"""
return {
"service": "MY Network Uploader Bot",
"version": "3.0.0",
"framework": "FastAPI",
"status": "running",
"uptime_seconds": int(time.time() - _app_start_time),
"api_docs": "/docs",
"health_check": "/api/system/health"
}
@app.get("/api")
async def api_info():
"""Информация об API"""
crypto_manager = get_ed25519_manager()
return {
"api_version": "v1",
"service": "uploader-bot",
"network": "MY Network v3.0",
"node_id": crypto_manager.node_id,
"capabilities": [
"content_upload",
"content_sync",
"decentralized_filtering",
"ed25519_signatures",
"web2_client_api"
],
"endpoints": {
"authentication": "/api/v1/auth/*",
"content": "/api/v1/content/*",
"storage": "/api/storage/*",
"node_communication": "/api/node/*",
"system": "/api/system/*"
}
}
# Совместимость со старыми Sanic роутами
@app.get("/api/v1/ping")
async def legacy_ping():
"""Legacy ping endpoint для совместимости"""
return {
"status": "ok",
"timestamp": time.time(),
"framework": "FastAPI"
}
@app.get("/favicon.ico")
async def favicon():
"""Заглушка для favicon"""
return JSONResponse(status_code=204, content=None)
def run_server():
"""
Запуск сервера для разработки
"""
settings = get_settings()
# Настройка логирования
configure_logging()
# Конфигурация uvicorn
config = {
"app": "app.fastapi_main:app",
"host": getattr(settings, 'HOST', '0.0.0.0'),
"port": getattr(settings, 'PORT', 8000),
"reload": getattr(settings, 'DEBUG', False),
"log_level": "info" if not getattr(settings, 'DEBUG', False) else "debug",
"access_log": True,
"server_header": False,
"date_header": False
}
print(f"🚀 Starting FastAPI server on {config['host']}:{config['port']}")
print(f"📚 API documentation: http://{config['host']}:{config['port']}/docs")
print(f"🔍 Health check: http://{config['host']}:{config['port']}/api/system/health")
uvicorn.run(**config)
if __name__ == "__main__":
run_server()