| """ |
| Background Worker Management API |
| Provides endpoints to manage and monitor the background data collection worker |
| """ |
|
|
| from fastapi import APIRouter, HTTPException |
| from fastapi.responses import JSONResponse |
| from typing import Dict, Any |
| import logging |
|
|
| from backend.workers.background_collector_worker import get_worker_instance |
| from backend.workers.comprehensive_db_collector_worker import get_comprehensive_db_worker |
| from backend.services.comprehensive_db_collector import collector_config, get_comprehensive_db_collector |
|
|
| logger = logging.getLogger(__name__) |
|
|
| router = APIRouter(prefix="/api/worker", tags=["Background Worker"]) |
|
|
|
|
| @router.get("/status") |
| async def get_worker_status(): |
| """ |
| Get background worker status and statistics |
| |
| Returns: |
| Worker status including collection stats, schedules, and recent errors |
| """ |
| try: |
| worker = await get_worker_instance() |
| stats = worker.get_stats() |
| |
| return JSONResponse(content={ |
| "success": True, |
| "worker_status": stats, |
| "message_fa": "وضعیت Worker به موفقیت دریافت شد", |
| "message_en": "Worker status retrieved successfully" |
| }) |
| |
| except Exception as e: |
| logger.error(f"Error getting worker status: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @router.post("/start") |
| async def start_worker(): |
| """ |
| Start the background worker |
| |
| Returns: |
| Success status |
| """ |
| try: |
| worker = await get_worker_instance() |
| |
| if worker.is_running: |
| return JSONResponse(content={ |
| "success": False, |
| "message_fa": "Worker در حال اجرا است", |
| "message_en": "Worker is already running" |
| }) |
| |
| worker.start() |
| |
| return JSONResponse(content={ |
| "success": True, |
| "message_fa": "Worker با موفقیت راهاندازی شد", |
| "message_en": "Worker started successfully", |
| "schedules": { |
| "ui_data": "Every 5 minutes", |
| "historical_data": "Every 15 minutes" |
| } |
| }) |
| |
| except Exception as e: |
| logger.error(f"Error starting worker: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @router.post("/stop") |
| async def stop_worker(): |
| """ |
| Stop the background worker |
| |
| Returns: |
| Success status |
| """ |
| try: |
| worker = await get_worker_instance() |
| |
| if not worker.is_running: |
| return JSONResponse(content={ |
| "success": False, |
| "message_fa": "Worker در حال اجرا نیست", |
| "message_en": "Worker is not running" |
| }) |
| |
| worker.stop() |
| |
| return JSONResponse(content={ |
| "success": True, |
| "message_fa": "Worker با موفقیت متوقف شد", |
| "message_en": "Worker stopped successfully" |
| }) |
| |
| except Exception as e: |
| logger.error(f"Error stopping worker: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @router.post("/force-collection") |
| async def force_collection(collection_type: str = "both"): |
| """ |
| Force immediate data collection |
| |
| Args: |
| collection_type: Type of collection ('ui', 'historical', or 'both') |
| |
| Returns: |
| Success status |
| """ |
| try: |
| if collection_type not in ['ui', 'historical', 'both']: |
| raise HTTPException( |
| status_code=400, |
| detail="Invalid collection_type. Must be 'ui', 'historical', or 'both'" |
| ) |
| |
| worker = await get_worker_instance() |
| |
| if not worker.is_running: |
| raise HTTPException( |
| status_code=400, |
| detail="Worker is not running. Start the worker first." |
| ) |
| |
| worker.force_collection(collection_type) |
| |
| return JSONResponse(content={ |
| "success": True, |
| "message_fa": f"جمعآوری {collection_type} با موفقیت آغاز شد", |
| "message_en": f"Manual {collection_type} collection started successfully", |
| "collection_type": collection_type |
| }) |
| |
| except HTTPException: |
| raise |
| except Exception as e: |
| logger.error(f"Error forcing collection: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @router.get("/stats") |
| async def get_collection_stats(): |
| """ |
| Get detailed collection statistics |
| |
| Returns: |
| Detailed statistics about data collections |
| """ |
| try: |
| worker = await get_worker_instance() |
| stats = worker.get_stats() |
| |
| return JSONResponse(content={ |
| "success": True, |
| "statistics": { |
| "total_ui_collections": stats['ui_collections'], |
| "total_historical_collections": stats['historical_collections'], |
| "total_records_saved": stats['total_records_saved'], |
| "last_ui_collection": stats['last_ui_collection'], |
| "last_historical_collection": stats['last_historical_collection'], |
| "average_records_per_ui_collection": ( |
| stats['total_records_saved'] / stats['ui_collections'] |
| if stats['ui_collections'] > 0 else 0 |
| ), |
| "average_records_per_historical_collection": ( |
| stats['total_records_saved'] / stats['historical_collections'] |
| if stats['historical_collections'] > 0 else 0 |
| ) |
| }, |
| "recent_errors": stats['recent_errors'], |
| "message_fa": "آمار جمعآوری دادهها", |
| "message_en": "Data collection statistics" |
| }) |
| |
| except Exception as e: |
| logger.error(f"Error getting collection stats: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @router.get("/schedules") |
| async def get_schedules(): |
| """ |
| Get current collection schedules |
| |
| Returns: |
| Schedule information for all collection jobs |
| """ |
| try: |
| worker = await get_worker_instance() |
| stats = worker.get_stats() |
| |
| return JSONResponse(content={ |
| "success": True, |
| "schedules": stats['scheduler_jobs'], |
| "message_fa": "زمانبندی جمعآوری دادهها", |
| "message_en": "Data collection schedules" |
| }) |
| |
| except Exception as e: |
| logger.error(f"Error getting schedules: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @router.get("/comprehensive/config") |
| async def comprehensive_collector_config(): |
| """Get comprehensive DB collector configuration (15-min unified sync).""" |
| cfg = collector_config() |
| return JSONResponse( |
| content={ |
| "success": True, |
| "config": cfg, |
| "message_fa": "تنظیمات جمعآوری جامع دیتابیس", |
| "message_en": "Comprehensive database collector configuration", |
| } |
| ) |
|
|
|
|
| @router.get("/comprehensive/status") |
| async def comprehensive_collector_status(): |
| """Status of the 15-minute comprehensive DB collector.""" |
| try: |
| worker = await get_comprehensive_db_worker() |
| return JSONResponse( |
| content={ |
| "success": True, |
| "comprehensive_worker": worker.get_stats(), |
| "message_fa": "وضعیت جمعآوری جامع دیتابیس", |
| "message_en": "Comprehensive database collector status", |
| } |
| ) |
| except Exception as e: |
| logger.error(f"Error getting comprehensive collector status: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @router.post("/comprehensive/start") |
| async def start_comprehensive_collector(): |
| """Start the comprehensive DB collector scheduler.""" |
| from backend.workers.comprehensive_db_collector_worker import start_comprehensive_db_worker |
|
|
| worker = await start_comprehensive_db_worker() |
| if not worker: |
| return JSONResponse( |
| content={ |
| "success": False, |
| "message_fa": "جمعآوری جامع غیرفعال است (COMPREHENSIVE_DB_COLLECTOR_ENABLED=false)", |
| "message_en": "Comprehensive collector is disabled via env", |
| } |
| ) |
| return JSONResponse( |
| content={ |
| "success": True, |
| "message_fa": "جمعآوری جامع دیتابیس راهاندازی شد", |
| "message_en": "Comprehensive DB collector started", |
| "config": collector_config(), |
| } |
| ) |
|
|
|
|
| @router.post("/comprehensive/stop") |
| async def stop_comprehensive_collector(): |
| """Stop the comprehensive DB collector scheduler.""" |
| from backend.workers.comprehensive_db_collector_worker import stop_comprehensive_db_worker |
|
|
| await stop_comprehensive_db_worker() |
| return JSONResponse( |
| content={ |
| "success": True, |
| "message_fa": "جمعآوری جامع دیتابیس متوقف شد", |
| "message_en": "Comprehensive DB collector stopped", |
| } |
| ) |
|
|
|
|
| @router.post("/comprehensive/run-now") |
| async def run_comprehensive_collector_now(): |
| """Force one immediate comprehensive DB collection cycle.""" |
| try: |
| result = await get_comprehensive_db_collector().run() |
| return JSONResponse( |
| content={ |
| "success": bool(result.get("success")), |
| "result": result, |
| "message_fa": "جمعآوری جامع فوری انجام شد", |
| "message_en": "Immediate comprehensive collection finished", |
| } |
| ) |
| except Exception as e: |
| logger.error(f"Comprehensive collection failed: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @router.post("/comprehensive/force-collection") |
| async def force_comprehensive_collection(): |
| """Schedule comprehensive collection on the worker queue.""" |
| worker = await get_comprehensive_db_worker() |
| if not worker.is_running: |
| raise HTTPException(status_code=400, detail="Comprehensive collector is not running") |
| worker.force_collection() |
| return JSONResponse( |
| content={ |
| "success": True, |
| "message_fa": "جمعآوری جامع زمانبندی شد", |
| "message_en": "Comprehensive collection scheduled", |
| } |
| ) |
|
|
|
|
| @router.get("/health") |
| async def worker_health_check(): |
| """ |
| Health check for background worker |
| |
| Returns: |
| Health status |
| """ |
| try: |
| worker = await get_worker_instance() |
| |
| is_healthy = worker.is_running |
| |
| return JSONResponse(content={ |
| "success": True, |
| "healthy": is_healthy, |
| "status": "running" if is_healthy else "stopped", |
| "message_fa": "Worker سالم است" if is_healthy else "Worker متوقف است", |
| "message_en": "Worker is healthy" if is_healthy else "Worker is stopped" |
| }) |
| |
| except Exception as e: |
| logger.error(f"Error in health check: {e}") |
| return JSONResponse( |
| status_code=503, |
| content={ |
| "success": False, |
| "healthy": False, |
| "status": "error", |
| "error": str(e), |
| "message_fa": "خطا در بررسی سلامت Worker", |
| "message_en": "Error checking worker health" |
| } |
| ) |
|
|