2025-12-14 23:45:41 +00:00
|
|
|
|
"""
|
|
|
|
|
|
Nakama Moderation Backend - Python FastAPI
|
|
|
|
|
|
"""
|
|
|
|
|
|
import os
|
|
|
|
|
|
from contextlib import asynccontextmanager
|
|
|
|
|
|
from fastapi import FastAPI
|
|
|
|
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
|
|
|
|
from fastapi.middleware.trustedhost import TrustedHostMiddleware
|
|
|
|
|
|
import uvicorn
|
|
|
|
|
|
|
|
|
|
|
|
from config import settings
|
|
|
|
|
|
from database import connect_db, close_db
|
2025-12-15 00:09:11 +00:00
|
|
|
|
# Import from middleware.py file (not directory)
|
|
|
|
|
|
import middleware as app_middleware
|
2025-12-14 23:45:41 +00:00
|
|
|
|
from routes.mod_app import router as mod_app_router
|
|
|
|
|
|
from routes.moderation_auth import router as moderation_auth_router
|
|
|
|
|
|
from websocket_server import get_socketio_app
|
|
|
|
|
|
from utils.minio_client import init_minio
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@asynccontextmanager
|
|
|
|
|
|
async def lifespan(app: FastAPI):
|
|
|
|
|
|
"""Lifecycle management"""
|
|
|
|
|
|
# Startup
|
|
|
|
|
|
print("\n" + "=" * 60)
|
|
|
|
|
|
print("🚀 Запуск сервера модерации (Python)...")
|
|
|
|
|
|
await connect_db()
|
|
|
|
|
|
|
|
|
|
|
|
# Initialize MinIO
|
|
|
|
|
|
if settings.MINIO_ENABLED:
|
|
|
|
|
|
init_minio()
|
|
|
|
|
|
|
2025-12-15 01:19:59 +00:00
|
|
|
|
# Log Socket.IO status
|
|
|
|
|
|
if socketio_app:
|
|
|
|
|
|
print("✅ WebSocket (Socket.IO) доступен")
|
|
|
|
|
|
print(" 📡 Namespace /mod-chat доступен для чата модераторов")
|
|
|
|
|
|
print(" 🔌 WebSocket endpoint: /socket.io")
|
|
|
|
|
|
else:
|
|
|
|
|
|
print("⚠️ WebSocket (Socket.IO) недоступен - чат модераторов не будет работать")
|
|
|
|
|
|
|
2025-12-14 23:45:41 +00:00
|
|
|
|
print("=" * 60 + "\n")
|
|
|
|
|
|
|
|
|
|
|
|
yield
|
|
|
|
|
|
|
|
|
|
|
|
# Shutdown
|
|
|
|
|
|
print("\n" + "=" * 60)
|
|
|
|
|
|
print("🛑 Остановка сервера модерации...")
|
|
|
|
|
|
await close_db()
|
|
|
|
|
|
print("=" * 60 + "\n")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app = FastAPI(
|
|
|
|
|
|
title="Nakama Moderation API",
|
|
|
|
|
|
description="API для модерации Nakama",
|
|
|
|
|
|
version="2.0.0",
|
|
|
|
|
|
lifespan=lifespan
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# CORS
|
|
|
|
|
|
app.add_middleware(
|
|
|
|
|
|
CORSMiddleware,
|
|
|
|
|
|
allow_origins=settings.MODERATION_CORS_ORIGIN.split(',') if settings.MODERATION_CORS_ORIGIN != '*' else ['*'],
|
|
|
|
|
|
allow_credentials=True,
|
|
|
|
|
|
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
|
|
|
|
|
allow_headers=["Content-Type", "Authorization", "x-telegram-init-data"],
|
|
|
|
|
|
max_age=86400,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# Setup middleware (security, logging, etc.)
|
2025-12-15 00:09:11 +00:00
|
|
|
|
app_middleware.setup_middleware(app)
|
2025-12-14 23:45:41 +00:00
|
|
|
|
|
|
|
|
|
|
# Health check
|
|
|
|
|
|
@app.get("/health")
|
|
|
|
|
|
async def health_check():
|
|
|
|
|
|
return {
|
|
|
|
|
|
"status": "ok",
|
|
|
|
|
|
"service": "moderation",
|
|
|
|
|
|
"version": "2.0.0-python"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# Root endpoint
|
|
|
|
|
|
@app.get("/")
|
|
|
|
|
|
async def root():
|
|
|
|
|
|
return {"message": "Nakama Moderation API - Python"}
|
|
|
|
|
|
|
|
|
|
|
|
# Include routers
|
|
|
|
|
|
app.include_router(mod_app_router, prefix="/api/mod-app", tags=["moderation"])
|
|
|
|
|
|
app.include_router(moderation_auth_router, prefix="/api/moderation-auth", tags=["auth"])
|
|
|
|
|
|
|
2025-12-15 01:19:59 +00:00
|
|
|
|
# Socket.IO будет обернут для правильной работы
|
|
|
|
|
|
# Создаем обернутое приложение для Socket.IO
|
|
|
|
|
|
# Socket.IO обрабатывает пути /socket.io, остальное идет в FastAPI
|
|
|
|
|
|
socketio_app = None
|
2025-12-15 00:37:34 +00:00
|
|
|
|
try:
|
|
|
|
|
|
socketio_app = get_socketio_app()
|
|
|
|
|
|
except Exception as e:
|
2025-12-15 01:19:59 +00:00
|
|
|
|
# Ошибка инициализации Socket.IO - используем только FastAPI
|
|
|
|
|
|
socketio_app = None
|
|
|
|
|
|
|
|
|
|
|
|
# Создаем обернутое приложение для Socket.IO
|
|
|
|
|
|
# Socket.IO обрабатывает пути /socket.io, остальное идет в FastAPI
|
|
|
|
|
|
class SocketIOWrapper:
|
|
|
|
|
|
"""Wrapper to combine FastAPI and Socket.IO ASGI apps"""
|
|
|
|
|
|
def __init__(self, fastapi_app, socketio_app):
|
|
|
|
|
|
self.fastapi_app = fastapi_app
|
|
|
|
|
|
self.socketio_app = socketio_app
|
|
|
|
|
|
|
|
|
|
|
|
async def __call__(self, scope, receive, send):
|
|
|
|
|
|
path = scope.get("path", "")
|
2025-12-15 01:25:31 +00:00
|
|
|
|
scope_type = scope.get("type", "")
|
|
|
|
|
|
|
2025-12-15 03:40:42 +00:00
|
|
|
|
# Логируем для отладки (только для WebSocket и /socket.io)
|
|
|
|
|
|
if scope_type == "websocket" or path.startswith("/socket.io"):
|
|
|
|
|
|
print(f"[SocketIOWrapper] Request: type={scope_type}, path={path}, method={scope.get('method', 'N/A')}")
|
2025-12-15 01:25:31 +00:00
|
|
|
|
|
|
|
|
|
|
# Если это WebSocket или путь начинается с /socket.io, используем Socket.IO
|
|
|
|
|
|
if scope_type == "websocket" or path.startswith("/socket.io"):
|
|
|
|
|
|
print(f"[SocketIOWrapper] ✅ Routing to Socket.IO: type={scope_type}, path={path}")
|
|
|
|
|
|
try:
|
2025-12-15 03:40:42 +00:00
|
|
|
|
# Socket.IO ASGI app должен обработать WebSocket upgrade
|
2025-12-15 01:25:31 +00:00
|
|
|
|
await self.socketio_app(scope, receive, send)
|
|
|
|
|
|
except Exception as e:
|
2025-12-15 03:40:42 +00:00
|
|
|
|
print(f"[SocketIOWrapper] ❌ Error in Socket.IO: {type(e).__name__}: {e}")
|
2025-12-15 01:25:31 +00:00
|
|
|
|
import traceback
|
|
|
|
|
|
traceback.print_exc()
|
2025-12-15 03:40:42 +00:00
|
|
|
|
# Для WebSocket ошибок не поднимаем исключение, чтобы не ломать соединение
|
|
|
|
|
|
if scope_type != "websocket":
|
|
|
|
|
|
raise
|
2025-12-15 01:19:59 +00:00
|
|
|
|
else:
|
|
|
|
|
|
# Иначе используем FastAPI
|
|
|
|
|
|
await self.fastapi_app(scope, receive, send)
|
|
|
|
|
|
|
|
|
|
|
|
# Создаем обернутое приложение (доступно для uvicorn)
|
|
|
|
|
|
# Это должно быть на уровне модуля, чтобы uvicorn мог импортировать
|
|
|
|
|
|
if socketio_app:
|
|
|
|
|
|
wrapped_app = SocketIOWrapper(app, socketio_app)
|
|
|
|
|
|
else:
|
|
|
|
|
|
# Если Socket.IO не инициализирован, используем только FastAPI
|
|
|
|
|
|
wrapped_app = app
|
2025-12-14 23:45:41 +00:00
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
|
print("\n" + "=" * 60)
|
|
|
|
|
|
print("✅ Сервер модерации запущен (Python)")
|
|
|
|
|
|
print(f" 🌐 API: http://0.0.0.0:{settings.MODERATION_PORT}/api")
|
|
|
|
|
|
print(f" 🔌 WebSocket: http://0.0.0.0:{settings.MODERATION_PORT}/socket.io")
|
|
|
|
|
|
print(f" 📦 MongoDB: {settings.MONGODB_URI.split('@')[-1] if '@' in settings.MONGODB_URI else settings.MONGODB_URI}")
|
|
|
|
|
|
print("=" * 60 + "\n")
|
|
|
|
|
|
|
|
|
|
|
|
uvicorn.run(
|
2025-12-15 01:16:04 +00:00
|
|
|
|
wrapped_app, # Используем обернутое приложение
|
2025-12-14 23:45:41 +00:00
|
|
|
|
host="0.0.0.0",
|
|
|
|
|
|
port=settings.MODERATION_PORT,
|
|
|
|
|
|
reload=settings.IS_DEVELOPMENT,
|
|
|
|
|
|
log_level="info"
|
|
|
|
|
|
)
|
|
|
|
|
|
|