from fastapi import FastAPI, Request, WebSocket from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import RedirectResponse from app.core.config import settings from app.db.database import async_engine as engine, Base from app.api import auth, products, orders, users, analytics, files, notifications, calendar, scheduler, maintenance, branches, staff_analytics, sessions from app.utils.rate_limiter import rate_limiter from app.utils.logger import log_api_request from app.utils.tasks import run_periodic_tasks, sync_pos_metrics_task from app.services.websocket import connect, disconnect from app.realtime.subscriber import subscribe_order_events from app.routes.websocket import websocket_endpoint, manager, router as websocket_router, staff_metrics_websocket import socketio import time import logging import asyncio from typing import List, Dict, Optional from app.db.models import ensure_tables # Configure logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # Create Socket.IO server sio = socketio.AsyncServer(async_mode='asgi', cors_allowed_origins='*') socket_app = socketio.ASGIApp(sio) app = FastAPI(title=settings.PROJECT_NAME, version=settings.VERSION, openapi_url=f"{settings.API_V1_STR}/openapi.json") # Store background tasks background_tasks = set() # Configure CORS app.add_middleware( CORSMiddleware, allow_origins=["http://localhost:5173", "http://localhost:3000"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Socket.IO event handlers @sio.event async def connect(sid: str, environ: dict, auth: Optional[dict] = None): logger.info(f"Client connected: {sid}") return True @sio.event async def disconnect(sid: str): logger.info(f"Client disconnected: {sid}") @sio.event async def message(sid: str, data: dict): logger.info(f"Message from {sid}: {data}") await sio.emit('message', {'response': 'Message received'}, room=sid) # Mount Socket.IO app app.mount("/socket.io", socket_app) @app.get("/api/v1/health") async def health_check() -> Dict[str, str]: """Public health check endpoint that doesn't require authentication""" return { "status": "healthy", "version": settings.VERSION, "service": settings.PROJECT_NAME } # WebSocket endpoint @app.websocket("/ws") async def websocket_handler(websocket: WebSocket): await websocket_endpoint(websocket) @app.websocket("/ws/orders") async def orders_websocket_handler(websocket: WebSocket): await websocket_endpoint(websocket) # Request logging and rate limiting middleware @app.middleware("http") async def middleware(request: Request, call_next): await rate_limiter.check_rate_limit(request) start_time = time.time() response = await call_next(request) end_time = time.time() duration = end_time - start_time log_api_request( method=request.method, path=request.url.path, status_code=response.status_code, duration=duration ) return response # Application startup and shutdown events @app.on_event("startup") async def startup_event(): # Create all database tables await ensure_tables() # Start Redis subscriber in background task = asyncio.create_task(subscribe_order_events(manager)) background_tasks.add(task) task.add_done_callback(background_tasks.discard) # Start periodic tasks task = asyncio.create_task(run_periodic_tasks()) background_tasks.add(task) task.add_done_callback(background_tasks.discard) # Start POS metrics sync task task = asyncio.create_task(sync_pos_metrics_task()) background_tasks.add(task) task.add_done_callback(background_tasks.discard) @app.on_event("shutdown") async def shutdown_event(): # Cancel background tasks for task in background_tasks: task.cancel() # Dashboard compatibility routes @app.get(f"{settings.API_V1_STR}/sales") async def sales_redirect(): return RedirectResponse(url=f"{settings.API_V1_STR}/analytics/sales") @app.get(f"{settings.API_V1_STR}/customers") async def customers_redirect(): return RedirectResponse(url=f"{settings.API_V1_STR}/analytics/customers") @app.get(f"{settings.API_V1_STR}/brands") async def brands_redirect(): return RedirectResponse(url=f"{settings.API_V1_STR}/analytics/brands") @app.get(f"{settings.API_V1_STR}/products") async def products_redirect(): return RedirectResponse(url=f"{settings.API_V1_STR}/analytics/products") # Include routers app.include_router(auth.router, prefix=f"{settings.API_V1_STR}/auth", tags=["auth"]) app.include_router(users.router, prefix=f"{settings.API_V1_STR}/users", tags=["users"]) app.include_router(products.router, prefix=f"{settings.API_V1_STR}/products", tags=["products"]) app.include_router(orders.router, prefix=f"{settings.API_V1_STR}/orders", tags=["orders"]) app.include_router(analytics.router, prefix=f"{settings.API_V1_STR}/analytics", tags=["analytics"]) app.include_router(files.router, prefix=f"{settings.API_V1_STR}/files", tags=["files"]) app.include_router(notifications.router, prefix=f"{settings.API_V1_STR}/notifications", tags=["notifications"]) app.include_router(calendar.router, prefix=f"{settings.API_V1_STR}/calendar", tags=["calendar"]) app.include_router(scheduler.router, prefix=f"{settings.API_V1_STR}/scheduler", tags=["scheduler"]) app.include_router(maintenance.router, prefix=f"{settings.API_V1_STR}/maintenance", tags=["maintenance"]) app.include_router(branches.router, prefix=f"{settings.API_V1_STR}/branches", tags=["branches"]) app.include_router(staff_analytics.router, prefix=f"{settings.API_V1_STR}/staff", tags=["staff"]) app.include_router(sessions.router, prefix=f"{settings.API_V1_STR}/sessions", tags=["sessions"]) app.include_router(websocket_router) @app.get("/") async def root(): return { "message": f"Welcome to {settings.PROJECT_NAME} v{settings.VERSION}", "docs_url": "/docs", "openapi_url": "/openapi.json" }