|
from fastapi import FastAPI, Depends, Request, HTTPException, status |
|
from fastapi.middleware.cors import CORSMiddleware |
|
from contextlib import asynccontextmanager |
|
import uvicorn |
|
import os |
|
import sys |
|
import logging |
|
from dotenv import load_dotenv |
|
from fastapi.responses import JSONResponse, PlainTextResponse |
|
from fastapi.staticfiles import StaticFiles |
|
import time |
|
import uuid |
|
import traceback |
|
|
|
|
|
logging.basicConfig( |
|
level=logging.INFO, |
|
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', |
|
handlers=[ |
|
logging.StreamHandler(sys.stdout), |
|
] |
|
) |
|
logger = logging.getLogger(__name__) |
|
|
|
|
|
load_dotenv() |
|
DEBUG = os.getenv("DEBUG", "False").lower() in ("true", "1", "t") |
|
|
|
|
|
required_env_vars = [ |
|
"AIVEN_DB_URL", |
|
"MONGODB_URL", |
|
"PINECONE_API_KEY", |
|
"PINECONE_INDEX_NAME", |
|
"GOOGLE_API_KEY" |
|
] |
|
|
|
missing_vars = [var for var in required_env_vars if not os.getenv(var)] |
|
if missing_vars: |
|
logger.error(f"Missing required environment variables: {', '.join(missing_vars)}") |
|
if not DEBUG: |
|
sys.exit(1) |
|
|
|
|
|
def check_database_connections(): |
|
"""Kiểm tra kết nối các database khi khởi động""" |
|
from app.database.postgresql import check_db_connection as check_postgresql |
|
from app.database.mongodb import check_db_connection as check_mongodb |
|
from app.database.pinecone import check_db_connection as check_pinecone |
|
|
|
db_status = { |
|
"postgresql": check_postgresql(), |
|
"mongodb": check_mongodb(), |
|
"pinecone": check_pinecone() |
|
} |
|
|
|
all_ok = all(db_status.values()) |
|
if not all_ok: |
|
failed_dbs = [name for name, status in db_status.items() if not status] |
|
logger.error(f"Failed to connect to databases: {', '.join(failed_dbs)}") |
|
if not DEBUG: |
|
sys.exit(1) |
|
|
|
return db_status |
|
|
|
|
|
@asynccontextmanager |
|
async def lifespan(app: FastAPI): |
|
|
|
logger.info("Starting application...") |
|
db_status = check_database_connections() |
|
|
|
|
|
if DEBUG and all(db_status.values()): |
|
from app.database.postgresql import create_tables |
|
if create_tables(): |
|
logger.info("Database tables created or already exist") |
|
|
|
yield |
|
|
|
|
|
logger.info("Shutting down application...") |
|
|
|
|
|
try: |
|
from app.api.mongodb_routes import router as mongodb_router |
|
from app.api.postgresql_routes import router as postgresql_router |
|
from app.api.rag_routes import router as rag_router |
|
from app.api.websocket_routes import router as websocket_router |
|
from app.api.pdf_routes import router as pdf_router |
|
from app.api.pdf_websocket import router as pdf_websocket_router |
|
|
|
|
|
from app.utils.middleware import RequestLoggingMiddleware, ErrorHandlingMiddleware, DatabaseCheckMiddleware |
|
|
|
|
|
from app.utils.debug_utils import debug_view, DebugInfo, error_tracker, performance_monitor |
|
|
|
|
|
from app.utils.cache import get_cache |
|
|
|
logger.info("Successfully imported all routers and modules") |
|
|
|
except ImportError as e: |
|
logger.error(f"Error importing routes or middlewares: {e}") |
|
raise |
|
|
|
|
|
app = FastAPI( |
|
title="PIX Project Backend API", |
|
description="Backend API for PIX Project with MongoDB, PostgreSQL and RAG integration", |
|
version="1.0.0", |
|
docs_url="/docs", |
|
redoc_url="/redoc", |
|
debug=DEBUG, |
|
lifespan=lifespan, |
|
) |
|
|
|
|
|
app.add_middleware( |
|
CORSMiddleware, |
|
allow_origins=["*"], |
|
allow_credentials=True, |
|
allow_methods=["*"], |
|
allow_headers=["*"], |
|
) |
|
|
|
|
|
app.add_middleware(ErrorHandlingMiddleware) |
|
app.add_middleware(RequestLoggingMiddleware) |
|
if not DEBUG: |
|
app.add_middleware(DatabaseCheckMiddleware) |
|
|
|
|
|
app.include_router(mongodb_router) |
|
app.include_router(postgresql_router) |
|
app.include_router(rag_router) |
|
app.include_router(websocket_router) |
|
app.include_router(pdf_router) |
|
app.include_router(pdf_websocket_router) |
|
|
|
|
|
logger.info("Registered API routes:") |
|
for route in app.routes: |
|
if hasattr(route, "path") and hasattr(route, "methods"): |
|
methods = ",".join(route.methods) |
|
logger.info(f" {methods:<10} {route.path}") |
|
|
|
|
|
@app.get("/") |
|
def read_root(): |
|
return { |
|
"message": "Welcome to PIX Project Backend API", |
|
"documentation": "/docs", |
|
} |
|
|
|
|
|
@app.get("/health") |
|
def health_check(): |
|
|
|
db_status = check_database_connections() |
|
all_db_ok = all(db_status.values()) |
|
|
|
return { |
|
"status": "healthy" if all_db_ok else "degraded", |
|
"version": "1.0.0", |
|
"environment": os.environ.get("ENVIRONMENT", "production"), |
|
"databases": db_status |
|
} |
|
|
|
@app.get("/api/ping") |
|
async def ping(): |
|
return {"status": "pong"} |
|
|
|
|
|
@app.get("/cache/stats") |
|
def cache_stats(): |
|
"""Trả về thống kê về cache""" |
|
cache = get_cache() |
|
return cache.stats() |
|
|
|
|
|
@app.delete("/cache/clear") |
|
def cache_clear(): |
|
"""Xóa tất cả dữ liệu trong cache""" |
|
cache = get_cache() |
|
cache.clear() |
|
return {"message": "Cache cleared successfully"} |
|
|
|
|
|
if DEBUG: |
|
@app.get("/debug/config") |
|
def debug_config(): |
|
"""Hiển thị thông tin cấu hình (chỉ trong chế độ debug)""" |
|
config = { |
|
"environment": os.environ.get("ENVIRONMENT", "production"), |
|
"debug": DEBUG, |
|
"db_connection_mode": os.environ.get("DB_CONNECTION_MODE", "aiven"), |
|
"databases": { |
|
"postgresql": os.environ.get("AIVEN_DB_URL", "").split("@")[1].split("/")[0] if "@" in os.environ.get("AIVEN_DB_URL", "") else "N/A", |
|
"mongodb": os.environ.get("MONGODB_URL", "").split("@")[1].split("/?")[0] if "@" in os.environ.get("MONGODB_URL", "") else "N/A", |
|
"pinecone": os.environ.get("PINECONE_INDEX_NAME", "N/A"), |
|
} |
|
} |
|
return config |
|
|
|
@app.get("/debug/system") |
|
def debug_system(): |
|
"""Hiển thị thông tin hệ thống (chỉ trong chế độ debug)""" |
|
return DebugInfo.get_system_info() |
|
|
|
@app.get("/debug/database") |
|
def debug_database(): |
|
"""Hiển thị trạng thái database (chỉ trong chế độ debug)""" |
|
return DebugInfo.get_database_status() |
|
|
|
@app.get("/debug/errors") |
|
def debug_errors(limit: int = 10): |
|
"""Hiển thị các lỗi gần đây (chỉ trong chế độ debug)""" |
|
return error_tracker.get_errors(limit=limit) |
|
|
|
@app.get("/debug/performance") |
|
def debug_performance(): |
|
"""Hiển thị thông tin hiệu suất (chỉ trong chế độ debug)""" |
|
return performance_monitor.get_report() |
|
|
|
@app.get("/debug/full") |
|
def debug_full_report(request: Request): |
|
"""Hiển thị báo cáo debug đầy đủ (chỉ trong chế độ debug)""" |
|
return debug_view(request) |
|
|
|
@app.get("/debug/cache") |
|
def debug_cache(): |
|
"""Hiển thị thông tin chi tiết về cache (chỉ trong chế độ debug)""" |
|
cache = get_cache() |
|
cache_stats = cache.stats() |
|
|
|
|
|
cache_keys = list(cache.cache.keys()) |
|
history_users = list(cache.user_history_queues.keys()) |
|
|
|
return { |
|
"stats": cache_stats, |
|
"keys": cache_keys, |
|
"history_users": history_users, |
|
"config": { |
|
"ttl": cache.ttl, |
|
"cleanup_interval": cache.cleanup_interval, |
|
"max_size": cache.max_size, |
|
"history_queue_size": os.getenv("HISTORY_QUEUE_SIZE", "10"), |
|
"history_cache_ttl": os.getenv("HISTORY_CACHE_TTL", "3600"), |
|
} |
|
} |
|
|
|
@app.get("/debug/websocket-routes") |
|
def debug_websocket_routes(): |
|
"""Hiển thị thông tin về các WebSocket route (chỉ trong chế độ debug)""" |
|
ws_routes = [] |
|
for route in app.routes: |
|
if "websocket" in str(route.__class__).lower(): |
|
ws_routes.append({ |
|
"path": route.path, |
|
"name": route.name, |
|
"endpoint": str(route.endpoint) |
|
}) |
|
return { |
|
"websocket_routes": ws_routes, |
|
"total_count": len(ws_routes) |
|
} |
|
|
|
@app.get("/debug/mock-status") |
|
def debug_mock_status(): |
|
"""Display current mock mode settings""" |
|
|
|
|
|
|
|
return { |
|
"mock_mode": False, |
|
"mock_env_variable": os.getenv("USE_MOCK_MODE", "false"), |
|
"debug_mode": DEBUG |
|
} |
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
port = int(os.environ.get("PORT", 7860)) |
|
uvicorn.run("app:app", host="0.0.0.0", port=port, reload=DEBUG) |
|
|