File size: 9,771 Bytes
ac0f906
 
 
 
 
 
 
 
c8b8c9b
 
 
 
 
ac0f906
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e83f5e9
ac0f906
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e83f5e9
c8b8c9b
ac0f906
 
 
 
 
 
 
e83f5e9
 
 
c8b8c9b
 
ac0f906
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e83f5e9
c8b8c9b
 
 
 
 
 
 
 
ac0f906
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e83f5e9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ac0f906
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e83f5e9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c8b8c9b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ac0f906
 
 
c8b8c9b
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
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

# Cấu hình logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.StreamHandler(sys.stdout),
    ]
)
logger = logging.getLogger(__name__)

# Load environment variables
load_dotenv()
DEBUG = os.getenv("DEBUG", "False").lower() in ("true", "1", "t")

# Kiểm tra các biến môi trường bắt buộc
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:  # Chỉ thoát nếu không ở chế độ debug
        sys.exit(1)

# Database health checks
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:  # Chỉ thoát nếu không ở chế độ debug
            sys.exit(1)
    
    return db_status

# Khởi tạo lifespan để kiểm tra kết nối database khi khởi động
@asynccontextmanager
async def lifespan(app: FastAPI):
    # Startup: kiểm tra kết nối các database
    logger.info("Starting application...")
    db_status = check_database_connections()
    
    # Khởi tạo bảng trong cơ sở dữ liệu (nếu chưa tồn tại)
    if DEBUG and all(db_status.values()):  # Chỉ khởi tạo bảng trong chế độ debug và khi tất cả kết nối DB thành công
        from app.database.postgresql import create_tables
        if create_tables():
            logger.info("Database tables created or already exist")
    
    yield
    
    # Shutdown
    logger.info("Shutting down application...")

# Import routers
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
    
    # Import middlewares
    from app.utils.middleware import RequestLoggingMiddleware, ErrorHandlingMiddleware, DatabaseCheckMiddleware
    
    # Import debug utilities
    from app.utils.debug_utils import debug_view, DebugInfo, error_tracker, performance_monitor
    
    # Import cache
    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

# Create FastAPI app
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,
)

# Configure CORS
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Thêm middlewares
app.add_middleware(ErrorHandlingMiddleware)
app.add_middleware(RequestLoggingMiddleware)
if not DEBUG:  # Chỉ thêm middleware kiểm tra database trong production
    app.add_middleware(DatabaseCheckMiddleware)

# Include routers
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)

# Log all registered routes
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}")

# Root endpoint
@app.get("/")
def read_root():
    return {
        "message": "Welcome to PIX Project Backend API",
        "documentation": "/docs",
    }

# Health check endpoint
@app.get("/health")
def health_check():
    # Kiểm tra kết nối database
    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"}

# Cache stats endpoint
@app.get("/cache/stats")
def cache_stats():
    """Trả về thống kê về cache"""
    cache = get_cache()
    return cache.stats()

# Cache clear endpoint
@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"}

# Debug endpoints (chỉ có trong chế độ debug)
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()
        
        # Thêm thông tin chi tiết về các key trong cache
        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"""
        # Import was: from app.api.pdf_routes import USE_MOCK_MODE
        # We've disabled mock mode
        
        return {
            "mock_mode": False,  # Disabled - using real database
            "mock_env_variable": os.getenv("USE_MOCK_MODE", "false"),
            "debug_mode": DEBUG
        }


# Run the app with uvicorn when executed directly
if __name__ == "__main__":
    port = int(os.environ.get("PORT", 7860))
    uvicorn.run("app:app", host="0.0.0.0", port=port, reload=DEBUG)