Really-amin commited on
Commit
568293e
·
verified ·
1 Parent(s): 469daa9

Update app/main.py

Browse files
Files changed (1) hide show
  1. app/main.py +393 -180
app/main.py CHANGED
@@ -1,226 +1,439 @@
1
-
2
- # --- Standard Library Imports ---
3
- import os
4
  import logging
 
 
5
  from pathlib import Path
6
  from contextlib import asynccontextmanager
7
 
8
- # --- FastAPI and Related Imports ---
9
- from fastapi import FastAPI, HTTPException, Depends, Request
10
  from fastapi.staticfiles import StaticFiles
11
  from fastapi.responses import HTMLResponse, FileResponse
12
  from fastapi.middleware.cors import CORSMiddleware
13
- from fastapi.middleware.gzip import GZipMiddleware
14
-
15
- # --- Application-Specific Imports ---
16
- # API Routers
17
- from .api import (
18
- auth,
19
- reports,
20
- documents,
21
- ocr,
22
- dashboard,
23
- scraping,
24
- analytics,
25
- enhanced_analytics
26
- )
27
-
28
- # Services
29
- from .services.database_service import DatabaseManager
30
- from .services.ocr_service import OCRPipeline
31
- from .services.ai_service import AIScoringEngine
32
- # Note: notification_service and cache_service were imported but not used in the lifespan.
33
- # They can be initialized here if needed.
34
-
35
- # --- Logging Configuration ---
 
 
 
36
  logging.basicConfig(
37
  level=logging.INFO,
38
- format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
 
 
 
 
39
  )
40
  logger = logging.getLogger(__name__)
41
 
 
 
 
 
 
 
 
42
 
43
- # --- Lifespan Management ---
44
  @asynccontextmanager
45
  async def lifespan(app: FastAPI):
46
- """
47
- Manages the application's lifespan. Initializes services on startup
48
- and cleans up on shutdown. Services are stored in app.state.
49
- """
50
- logger.info("🚀 Starting Legal Dashboard...")
51
-
52
  try:
53
- # Initialize services and store them in the application state
54
- logger.info("📦 Initializing services...")
55
- app.state.db_manager = DatabaseManager()
56
- app.state.db_manager.initialize()
57
- logger.info(" Database initialized")
58
-
59
- app.state.ocr_pipeline = OCRPipeline()
60
- app.state.ocr_pipeline.initialize()
61
- logger.info("✅ OCR Pipeline initialized")
62
-
63
- app.state.ai_engine = AIScoringEngine()
64
- logger.info("✅ AI Engine initialized")
65
-
66
- # Create required temporary directories
67
- Path("/tmp/uploads").mkdir(exist_ok=True)
68
- Path("/tmp/data").mkdir(exist_ok=True)
69
-
70
- logger.info("🎉 All services initialized successfully!")
71
-
72
- yield # Application runs here
73
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  except Exception as e:
75
- logger.critical(f"❌ Initialization failed: {e}", exc_info=True)
76
- # In a real-world scenario, you might want to handle this more gracefully
77
- # or prevent the app from starting if critical services fail.
78
  finally:
79
- # Cleanup can be added here if needed (e.g., closing connections)
80
- logger.info("🔄 Shutting down Legal Dashboard...")
81
 
 
 
 
 
 
 
 
 
 
82
 
83
- # --- FastAPI Application Instance ---
84
  app = FastAPI(
85
  title="Legal Dashboard API",
86
- description="AI-powered Persian legal document processing system",
87
- version="1.0.0",
88
  docs_url="/api/docs",
89
  redoc_url="/api/redoc",
90
  lifespan=lifespan
91
  )
92
 
93
- # --- Middlewares ---
94
- app.add_middleware(GZipMiddleware, minimum_size=1000)
 
 
 
 
 
95
  app.add_middleware(
96
  CORSMiddleware,
97
- allow_origins=["*"], # WARNING: Configure this properly for production
98
  allow_credentials=True,
99
  allow_methods=["*"],
100
  allow_headers=["*"],
101
  )
102
 
103
-
104
- # --- Dependency Injection Providers ---
105
- def get_db_manager(request: Request) -> DatabaseManager:
106
- return request.app.state.db_manager
107
-
108
- def get_ocr_pipeline(request: Request) -> OCRPipeline:
109
- return request.app.state.ocr_pipeline
110
-
111
- def get_ai_engine(request: Request) -> AIScoringEngine:
112
- return request.app.state.ai_engine
113
-
114
-
115
- # --- API Routers ---
116
- app.include_router(
117
- documents.router, prefix="/api/documents", tags=["Documents"])
118
- app.include_router(ocr.router, prefix="/api/ocr", tags=["OCR"])
119
- app.include_router(
120
- dashboard.router, prefix="/api/dashboard", tags=["Dashboard"])
121
- app.include_router(scraping.router, prefix="/api/scraping", tags=["Scraping"])
122
- app.include_router(
123
- analytics.router, prefix="/api/analytics", tags=["Analytics"])
124
- app.include_router(
125
- enhanced_analytics.router, prefix="/api/enhanced-analytics", tags=["Enhanced Analytics"])
126
- app.include_router(auth.router, prefix="/api/auth", tags=["Authentication"])
127
- app.include_router(reports.router, prefix="/api/reports",
128
- tags=["Reports & Analytics"])
129
-
130
-
131
- # --- Static Files and Frontend Serving ---
132
- frontend_dir = Path(__file__).resolve().parent.parent / "frontend"
133
- if frontend_dir.is_dir():
134
- app.mount(
135
- "/static", StaticFiles(directory=frontend_dir), name="static")
136
- logger.info(f"📁 Static files mounted from: {frontend_dir}")
137
  else:
138
- logger.warning(
139
- f"⚠️ Frontend directory not found at {frontend_dir}. The frontend will not be served.")
140
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
 
142
- @app.get("/", response_class=HTMLResponse, include_in_schema=False)
143
- async def read_root():
144
- """Serves the main frontend application or a fallback page."""
145
- html_file = frontend_dir / "index.html"
146
- if html_file.is_file():
147
- return FileResponse(html_file, media_type="text/html")
 
 
 
 
 
 
 
148
  else:
149
- return HTMLResponse("""
150
- <html>
151
- <head><title>Legal Dashboard</title></head>
152
- <body>
153
- <h1>🏛️ Legal Dashboard API</h1>
154
- <p>Backend is running, but the frontend file (index.html) was not found.</p>
155
- <p>Check the 'frontend' directory or <a href="/api/docs">📖 see the API Documentation</a>.</p>
156
- </body>
157
- </html>
158
- """)
159
-
160
-
161
- # --- Core API Endpoints ---
162
- @app.get("/api/health")
163
- async def health_check(
164
- db: DatabaseManager = Depends(get_db_manager),
165
- ocr: OCRPipeline = Depends(get_ocr_pipeline),
166
- ai: AIScoringEngine = Depends(get_ai_engine)
167
- ):
168
- """Checks the health of all critical services."""
169
- db_healthy = db.is_connected()
170
- ocr_healthy = ocr.initialized
171
- ai_healthy = ai is not None # Basic check, can be improved if AI engine has a health method
172
-
173
- if db_healthy and ocr_healthy and ai_healthy:
174
- status = "healthy"
175
  else:
176
- status = "unhealthy"
177
-
 
 
 
 
 
 
178
  return {
179
- "status": status,
180
- "services": {
181
- "database": "healthy" if db_healthy else "unhealthy",
182
- "ocr": "healthy" if ocr_healthy else "unhealthy",
183
- "ai": "healthy" if ai_healthy else "unhealthy"
184
- },
185
- "version": app.version
186
  }
187
 
188
-
189
- # --- Custom Exception Handlers ---
190
- @app.exception_handler(404)
191
- async def not_found_handler(request: Request, exc: HTTPException):
192
- """Custom handler for 404 Not Found errors."""
193
- return HTMLResponse(content="""
194
- <html>
195
- <head><title>404 - صفحه یافت نشد</title></head>
196
- <body style="font-family: 'Tahoma', sans-serif; text-align: center; padding: 50px;">
197
- <h1>🔍 صفحه یافت نشد</h1>
198
- <p>متأسفانه، آدرس درخواستی شما در این سرور وجود ندارد.</p>
199
- <a href="/">🏠 بازگشت به صفحه اصلی</a>
200
- </body>
201
- </html>
202
- """, status_code=404)
203
-
204
-
205
- @app.exception_handler(500)
206
- async def internal_error_handler(request: Request, exc: Exception):
207
- """Custom handler for 500 Internal Server Errors."""
208
- logger.error(f"Internal server error on request {request.method} {request.url}: {exc}", exc_info=True)
209
- return HTMLResponse(content="""
210
- <html>
211
- <head><title>500 - خطای داخلی سرور</title></head>
212
- <body style="font-family: 'Tahoma', sans-serif; text-align: center; padding: 50px;">
213
- <h1>⚠️ خطای سرور</h1>
214
- <p>متأسفانه یک خطای پیش‌بینی نشده در سرور رخ داده است. تیم فنی در حال بررسی مشکل است.</p>
215
- <a href="/">🏠 بازگشت به صفحه اصلی</a>
216
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
217
  </html>
218
- """, status_code=500)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
219
 
 
 
220
 
221
- # --- Development Server Runner ---
222
  if __name__ == "__main__":
223
  import uvicorn
224
- # This block is for development purposes only.
225
- # For production, use a proper process manager like Gunicorn with Uvicorn workers.
226
- uvicorn.run(app, host="0.0.0.0", port=8000)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import logging
2
+ import os
3
+ import asyncio
4
  from pathlib import Path
5
  from contextlib import asynccontextmanager
6
 
7
+ from fastapi import FastAPI, HTTPException, Request
 
8
  from fastapi.staticfiles import StaticFiles
9
  from fastapi.responses import HTMLResponse, FileResponse
10
  from fastapi.middleware.cors import CORSMiddleware
11
+ from fastapi.middleware.trustedhost import TrustedHostMiddleware
12
+
13
+ # Import API routers
14
+ from app.api import auth, documents, analytics, reports, ocr, scraping, dashboard, enhanced_analytics
15
+
16
+ # Import services - with error handling
17
+ try:
18
+ from app.services.database_service import DatabaseService
19
+ except ImportError as e:
20
+ logging.warning(f"Could not import DatabaseService: {e}")
21
+ DatabaseService = None
22
+
23
+ try:
24
+ from app.services.ocr_service import OCRService
25
+ except ImportError as e:
26
+ logging.warning(f"Could not import OCRService: {e}")
27
+ OCRService = None
28
+
29
+ try:
30
+ from app.services.ai_service import AIService
31
+ except ImportError as e:
32
+ logging.warning(f"Could not import AIService: {e}")
33
+ AIService = None
34
+
35
+ # Configure logging
36
+ os.makedirs('/app/logs', exist_ok=True)
37
  logging.basicConfig(
38
  level=logging.INFO,
39
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
40
+ handlers=[
41
+ logging.FileHandler('/app/logs/app.log', mode='a'),
42
+ logging.StreamHandler()
43
+ ]
44
  )
45
  logger = logging.getLogger(__name__)
46
 
47
+ # Initialize global services
48
+ database_service = None
49
+ ocr_service = None
50
+ ai_service = None
51
+
52
+ # Application startup flag
53
+ app_ready = False
54
 
 
55
  @asynccontextmanager
56
  async def lifespan(app: FastAPI):
57
+ """Application lifespan manager with optimized startup"""
58
+ global database_service, ocr_service, ai_service, app_ready
59
+
 
 
 
60
  try:
61
+ logger.info("🚀 Starting Legal Dashboard...")
62
+ logger.info("📦 Initializing core services...")
63
+
64
+ # Create necessary directories
65
+ directories = ["/app/data", "/app/logs", "/app/uploads", "/app/cache", "/app/backups"]
66
+ for directory in directories:
67
+ os.makedirs(directory, exist_ok=True)
68
+
69
+ # Initialize Database Service (Priority 1 - Required)
70
+ if DatabaseService:
71
+ try:
72
+ database_service = DatabaseService()
73
+ await database_service.initialize()
74
+ logger.info("✅ Database initialized")
75
+ except Exception as e:
76
+ logger.error(f"❌ Database initialization failed: {e}")
77
+ # Continue with limited functionality
78
+ else:
79
+ logger.warning("⚠️ DatabaseService not available")
80
+
81
+ # Initialize AI Service (Priority 2)
82
+ if AIService:
83
+ try:
84
+ ai_service = AIService()
85
+ logger.info("✅ AI Engine initialized")
86
+ except Exception as e:
87
+ logger.warning(f"⚠️ AI initialization failed: {e}")
88
+ else:
89
+ logger.warning("⚠️ AIService not available")
90
+
91
+ # Set app as ready (before OCR to avoid timeout)
92
+ app_ready = True
93
+ logger.info("🎉 Core services initialized! App is ready to serve requests.")
94
+
95
+ # Initialize OCR Service (Priority 3 - Can be loaded in background)
96
+ if OCRService:
97
+ try:
98
+ logger.info("🔄 Starting OCR service initialization in background...")
99
+ # Use asyncio to initialize OCR in background
100
+ asyncio.create_task(initialize_ocr_background())
101
+ except Exception as e:
102
+ logger.warning(f"⚠️ OCR background initialization failed: {e}")
103
+ else:
104
+ logger.warning("⚠️ OCRService not available")
105
+
106
+ yield
107
+
108
  except Exception as e:
109
+ logger.error(f"❌ Application startup failed: {e}")
110
+ app_ready = True # Set ready anyway to avoid timeout
111
+ yield
112
  finally:
113
+ logger.info("🔄 Shutting down services...")
 
114
 
115
+ async def initialize_ocr_background():
116
+ """Initialize OCR service in background to avoid startup timeout"""
117
+ global ocr_service
118
+ try:
119
+ logger.info("🔄 Loading OCR models in background...")
120
+ ocr_service = OCRService()
121
+ logger.info("✅ OCR Pipeline initialized in background")
122
+ except Exception as e:
123
+ logger.warning(f"⚠️ Background OCR initialization failed: {e}")
124
 
125
+ # Create FastAPI application
126
  app = FastAPI(
127
  title="Legal Dashboard API",
128
+ description="Advanced Legal Document Management System",
129
+ version="2.0.0",
130
  docs_url="/api/docs",
131
  redoc_url="/api/redoc",
132
  lifespan=lifespan
133
  )
134
 
135
+ # Add security middleware
136
+ app.add_middleware(
137
+ TrustedHostMiddleware,
138
+ allowed_hosts=["*"] # Configure appropriately for production
139
+ )
140
+
141
+ # Add CORS middleware
142
  app.add_middleware(
143
  CORSMiddleware,
144
+ allow_origins=["*"], # Configure appropriately for production
145
  allow_credentials=True,
146
  allow_methods=["*"],
147
  allow_headers=["*"],
148
  )
149
 
150
+ # Mount static files
151
+ frontend_path = Path("/app/frontend")
152
+ if frontend_path.exists():
153
+ app.mount("/static", StaticFiles(directory=str(frontend_path)), name="static")
154
+ logger.info(f"📁 Static files mounted from: {frontend_path}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
155
  else:
156
+ logger.warning(f"⚠️ Frontend directory not found: {frontend_path}")
157
+
158
+ # Include API routers with error handling
159
+ try:
160
+ app.include_router(auth.router, prefix="/api/auth", tags=["Authentication"])
161
+ app.include_router(documents.router, prefix="/api/documents", tags=["Documents"])
162
+ app.include_router(analytics.router, prefix="/api/analytics", tags=["Analytics"])
163
+ app.include_router(enhanced_analytics.router, prefix="/api/enhanced-analytics", tags=["Enhanced Analytics"])
164
+ app.include_router(reports.router, prefix="/api/reports", tags=["Reports"])
165
+ app.include_router(ocr.router, prefix="/api/ocr", tags=["OCR"])
166
+ app.include_router(scraping.router, prefix="/api/scraping", tags=["Scraping"])
167
+ app.include_router(dashboard.router, prefix="/api/dashboard", tags=["Dashboard"])
168
+ except Exception as e:
169
+ logger.error(f"Error including routers: {e}")
170
+
171
+ # Health check endpoint - Optimized for quick response
172
+ @app.get("/health")
173
+ async def health_check():
174
+ """Quick health check endpoint for Docker healthcheck"""
175
+ return {
176
+ "status": "healthy" if app_ready else "starting",
177
+ "message": "Legal Dashboard is running" if app_ready else "Legal Dashboard is starting up",
178
+ "app_ready": app_ready,
179
+ "services": {
180
+ "database": database_service is not None,
181
+ "ocr": ocr_service is not None,
182
+ "ai": ai_service is not None
183
+ }
184
+ }
185
 
186
+ # Detailed health check
187
+ @app.get("/health/detailed")
188
+ async def detailed_health_check():
189
+ """Detailed health check with service status"""
190
+ services_status = {}
191
+
192
+ # Check database
193
+ if database_service:
194
+ try:
195
+ # Quick database test
196
+ services_status["database"] = {"status": "healthy", "message": "Connected"}
197
+ except Exception as e:
198
+ services_status["database"] = {"status": "unhealthy", "message": str(e)}
199
  else:
200
+ services_status["database"] = {"status": "unavailable", "message": "Service not loaded"}
201
+
202
+ # Check OCR
203
+ if ocr_service:
204
+ services_status["ocr"] = {"status": "healthy", "message": "Models loaded"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
  else:
206
+ services_status["ocr"] = {"status": "loading" if not app_ready else "degraded", "message": "Models still loading or failed"}
207
+
208
+ # Check AI
209
+ if ai_service:
210
+ services_status["ai"] = {"status": "healthy", "message": "Service available"}
211
+ else:
212
+ services_status["ai"] = {"status": "unavailable", "message": "Service not loaded"}
213
+
214
  return {
215
+ "status": "healthy",
216
+ "app_ready": app_ready,
217
+ "services": services_status,
218
+ "uptime": "Running"
 
 
 
219
  }
220
 
221
+ # API health check
222
+ @app.get("/api/auth/health")
223
+ async def api_health():
224
+ """API health check for Docker healthcheck"""
225
+ return {"status": "healthy", "api": "running", "ready": app_ready}
226
+
227
+ # Simple ping endpoint
228
+ @app.get("/ping")
229
+ async def ping():
230
+ """Simple ping endpoint"""
231
+ return {"ping": "pong", "ready": app_ready}
232
+
233
+ # Root endpoint - serve main dashboard
234
+ @app.get("/", response_class=HTMLResponse)
235
+ async def root(request: Request):
236
+ """Serve the main dashboard"""
237
+ try:
238
+ index_path = frontend_path / "index.html"
239
+ if index_path.exists():
240
+ return FileResponse(str(index_path))
241
+ else:
242
+ # Fallback HTML if index.html doesn't exist
243
+ return HTMLResponse(generate_fallback_html())
244
+ except Exception as e:
245
+ logger.error(f"Error serving root: {e}")
246
+ return HTMLResponse(generate_fallback_html())
247
+
248
+ def generate_fallback_html():
249
+ """Generate fallback HTML when index.html is not available"""
250
+ return """
251
+ <!DOCTYPE html>
252
+ <html lang="fa" dir="rtl">
253
+ <head>
254
+ <title>Legal Dashboard | داشبورد حقوقی</title>
255
+ <meta charset="UTF-8">
256
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
257
+ <link href="https://fonts.googleapis.com/css2?family=Vazir:wght@300;400;500;600;700&display=swap" rel="stylesheet">
258
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.3.0/css/bootstrap.min.css" rel="stylesheet">
259
+ <style>
260
+ * { font-family: 'Vazir', sans-serif; }
261
+ body {
262
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
263
+ min-height: 100vh;
264
+ direction: rtl;
265
+ }
266
+ .container {
267
+ max-width: 1200px;
268
+ margin: 50px auto;
269
+ background: rgba(255,255,255,0.95);
270
+ padding: 40px;
271
+ border-radius: 15px;
272
+ box-shadow: 0 8px 32px rgba(0,0,0,0.1);
273
+ }
274
+ .status-badge {
275
+ display: inline-block;
276
+ padding: 5px 15px;
277
+ border-radius: 20px;
278
+ font-size: 0.9rem;
279
+ margin: 5px;
280
+ }
281
+ .status-healthy { background: #28a745; color: white; }
282
+ .status-loading { background: #ffc107; color: black; }
283
+ .nav-grid {
284
+ display: grid;
285
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
286
+ gap: 20px;
287
+ margin-top: 30px;
288
+ }
289
+ .nav-card {
290
+ background: #f8f9fa;
291
+ padding: 20px;
292
+ border-radius: 10px;
293
+ text-align: center;
294
+ text-decoration: none;
295
+ color: #333;
296
+ transition: transform 0.3s;
297
+ }
298
+ .nav-card:hover {
299
+ transform: translateY(-5px);
300
+ color: #333;
301
+ box-shadow: 0 5px 15px rgba(0,0,0,0.1);
302
+ }
303
+ </style>
304
+ </head>
305
+ <body>
306
+ <div class="container">
307
+ <div class="text-center mb-4">
308
+ <h1 class="display-4 mb-3">🏛️ داشبورد حقوقی</h1>
309
+ <p class="lead">سیستم پیشرفته مدیریت اسناد حقوقی</p>
310
+
311
+ <div class="mt-4">
312
+ <span class="status-badge status-healthy">سیستم آنلاین</span>
313
+ <span class="status-badge status-loading" id="ocrStatus">OCR در حال بارگذاری...</span>
314
+ </div>
315
+ </div>
316
+
317
+ <div class="nav-grid">
318
+ <a href="/api/docs" class="nav-card">
319
+ <h5>📚 مستندات API</h5>
320
+ <p>مشاهده و تست API endpoints</p>
321
+ </a>
322
+
323
+ <a href="/health/detailed" class="nav-card">
324
+ <h5>🔍 وضعیت سیستم</h5>
325
+ <p>بررسی جزئیات سلامت سرویس‌ها</p>
326
+ </a>
327
+
328
+ <a href="/static/documents.html" class="nav-card">
329
+ <h5>📄 مدیریت اسناد</h5>
330
+ <p>آپلود و مدیریت فایل‌ها</p>
331
+ </a>
332
+
333
+ <a href="/static/analytics.html" class="nav-card">
334
+ <h5>📊 تحلیل و آمار</h5>
335
+ <p>نمودارها و گزارشات</p>
336
+ </a>
337
+ </div>
338
+
339
+ <div class="text-center mt-4">
340
+ <button class="btn btn-primary" onclick="location.reload()">🔄 تازه‌سازی</button>
341
+ <button class="btn btn-outline-primary" onclick="checkStatus()">📊 بررسی وضعیت</button>
342
+ </div>
343
+
344
+ <div id="statusResult" class="mt-4"></div>
345
+ </div>
346
+
347
+ <script>
348
+ // Check OCR status
349
+ setTimeout(() => {
350
+ fetch('/health/detailed')
351
+ .then(r => r.json())
352
+ .then(data => {
353
+ const ocrStatus = document.getElementById('ocrStatus');
354
+ if (data.services?.ocr?.status === 'healthy') {
355
+ ocrStatus.textContent = 'OCR آماده';
356
+ ocrStatus.className = 'status-badge status-healthy';
357
+ }
358
+ })
359
+ .catch(() => {});
360
+ }, 5000);
361
+
362
+ function checkStatus() {
363
+ const result = document.getElementById('statusResult');
364
+ result.innerHTML = '<p>در حال بررسی...</p>';
365
+
366
+ fetch('/health/detailed')
367
+ .then(r => r.json())
368
+ .then(data => {
369
+ let html = '<div class="alert alert-info"><h5>وضعیت سرویس‌ها:</h5><ul>';
370
+ for (const [service, status] of Object.entries(data.services)) {
371
+ html += `<li><strong>${service}:</strong> ${status.status} - ${status.message}</li>`;
372
+ }
373
+ html += '</ul></div>';
374
+ result.innerHTML = html;
375
+ })
376
+ .catch(e => {
377
+ result.innerHTML = '<div class="alert alert-danger">خطا در دریافت وضعیت</div>';
378
+ });
379
+ }
380
+ </script>
381
+ </body>
382
  </html>
383
+ """
384
+
385
+ # Catch-all route for frontend SPA routing
386
+ @app.get("/{path:path}", response_class=HTMLResponse)
387
+ async def catch_all(path: str):
388
+ """Serve static files or fallback to index"""
389
+ try:
390
+ # Try to serve static file first
391
+ file_path = frontend_path / path
392
+ if file_path.exists() and file_path.is_file():
393
+ return FileResponse(str(file_path))
394
+
395
+ # If it's an HTML request, serve the appropriate HTML file
396
+ if path.endswith('.html'):
397
+ html_path = frontend_path / path
398
+ if html_path.exists():
399
+ return FileResponse(str(html_path))
400
+
401
+ # Fallback to main page for SPA routing
402
+ index_path = frontend_path / "index.html"
403
+ if index_path.exists():
404
+ return FileResponse(str(index_path))
405
+
406
+ # Final fallback
407
+ raise HTTPException(status_code=404, detail="Page not found")
408
+
409
+ except HTTPException:
410
+ raise
411
+ except Exception as e:
412
+ logger.error(f"Error in catch-all route: {e}")
413
+ raise HTTPException(status_code=500, detail="Internal server error")
414
+
415
+ # Make services available globally
416
+ def get_database_service():
417
+ return database_service
418
+
419
+ def get_ocr_service():
420
+ return ocr_service
421
 
422
+ def get_ai_service():
423
+ return ai_service
424
 
 
425
  if __name__ == "__main__":
426
  import uvicorn
427
+
428
+ # Check if running in container
429
+ in_container = os.path.exists('/.dockerenv')
430
+
431
+ uvicorn.run(
432
+ "app.main:app",
433
+ host="0.0.0.0",
434
+ port=8000,
435
+ reload=False,
436
+ workers=1,
437
+ access_log=True,
438
+ log_level="info"
439
+ )