Spaces:
Sleeping
Sleeping
| """ | |
| Main FastAPI application for POS Microservice. | |
| """ | |
| import logging | |
| import os | |
| from fastapi import FastAPI, Request, status | |
| from fastapi.exceptions import RequestValidationError | |
| from starlette.exceptions import HTTPException as StarletteHTTPException | |
| from fastapi.responses import JSONResponse | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from pymongo.errors import PyMongoError, ConnectionFailure, OperationFailure | |
| from jose import JWTError | |
| from app.core.config import settings | |
| from app.core.logging import get_logger | |
| from app.utils.response import error_response | |
| from app.middleware.logging_middleware import RequestLoggingMiddleware | |
| from app.nosql import connect_to_mongo, close_mongo_connection | |
| from app.sql import connect_to_database, disconnect_from_database | |
| from app.staff.controllers.router import router as staff_router | |
| from app.catalogue_services.controllers.router import router as catalogue_services_router | |
| from app.customers.controllers.router import router as customers_router | |
| from app.sales.retail.controllers.router import router as sales_router | |
| from app.appointments.controllers.router import router as appointments_router | |
| logger = get_logger(__name__) | |
| logging.basicConfig(level=logging.INFO) | |
| # Create FastAPI app | |
| app = FastAPI( | |
| title="POS Microservice", | |
| description="Point of Sale System - Sales, Inventory, Customer & Payment Management", | |
| version="1.0.0", | |
| docs_url="/docs", | |
| redoc_url="/redoc", | |
| root_path=os.getenv("ROOT_PATH", ""), | |
| ) | |
| # Request logging middleware | |
| app.add_middleware(RequestLoggingMiddleware) | |
| # CORS middleware | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], # Configure properly for production | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # Global Exception Handlers | |
| async def validation_exception_handler(request: Request, exc: RequestValidationError): | |
| """Handle validation errors""" | |
| errors = [ | |
| { | |
| "field": " -> ".join(str(loc) for loc in error["loc"]), | |
| "message": error["msg"], | |
| "type": error["type"] | |
| } | |
| for error in exc.errors() | |
| ] | |
| logger.warning( | |
| "Validation error", | |
| extra={ | |
| "path": request.url.path, | |
| "method": request.method, | |
| "error_count": len(errors), | |
| "errors": errors | |
| } | |
| ) | |
| return JSONResponse( | |
| status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, | |
| content=error_response( | |
| error="Validation Error", | |
| detail="The request contains invalid data", | |
| errors=errors, | |
| request_id=getattr(request.state, "request_id", None) | |
| ) | |
| ) | |
| async def jwt_exception_handler(request: Request, exc: JWTError): | |
| """Handle JWT errors""" | |
| logger.warning( | |
| "JWT authentication failed", | |
| extra={ | |
| "path": request.url.path, | |
| "error": str(exc), | |
| "client_ip": request.client.host if request.client else None | |
| } | |
| ) | |
| return JSONResponse( | |
| status_code=status.HTTP_401_UNAUTHORIZED, | |
| content=error_response( | |
| error="Unauthorized", | |
| detail="Invalid or expired token", | |
| request_id=getattr(request.state, "request_id", None) | |
| ) | |
| ) | |
| async def not_found_exception_handler(request: Request, exc: Exception): | |
| """Handle 404 errors""" | |
| return JSONResponse( | |
| status_code=status.HTTP_404_NOT_FOUND, | |
| content=error_response( | |
| error="Not Found", | |
| detail="Resource not found", | |
| request_id=getattr(request.state, "request_id", None) | |
| ) | |
| ) | |
| async def http_exception_handler(request: Request, exc: StarletteHTTPException): | |
| """Handle standard HTTP exceptions""" | |
| error_title = "Error" | |
| headers = None | |
| if exc.status_code == 401: | |
| error_title = "Authentication Error" | |
| headers = {"WWW-Authenticate": "Bearer"} | |
| elif exc.status_code == 403: | |
| error_title = "Permission Denied" | |
| elif exc.status_code == 404: | |
| error_title = "Not Found" | |
| elif exc.status_code == 422: | |
| error_title = "Validation Error" | |
| return JSONResponse( | |
| status_code=exc.status_code, | |
| content=error_response( | |
| error=error_title, | |
| detail=str(exc.detail), | |
| request_id=getattr(request.state, "request_id", None), | |
| headers=headers | |
| ) | |
| ) | |
| async def mongodb_exception_handler(request: Request, exc: PyMongoError): | |
| """Handle MongoDB errors""" | |
| logger.error( | |
| "Database error", | |
| extra={ | |
| "path": request.url.path, | |
| "error": str(exc), | |
| "error_type": type(exc).__name__ | |
| }, | |
| exc_info=True | |
| ) | |
| if isinstance(exc, ConnectionFailure): | |
| status_code = status.HTTP_503_SERVICE_UNAVAILABLE | |
| detail = "Database connection failed" | |
| elif isinstance(exc, OperationFailure): | |
| status_code = status.HTTP_500_INTERNAL_SERVER_ERROR | |
| detail = "Database operation failed" | |
| else: | |
| status_code = status.HTTP_500_INTERNAL_SERVER_ERROR | |
| detail = "Database error occurred" | |
| return JSONResponse( | |
| status_code=status_code, | |
| content=error_response( | |
| error="Database Error", | |
| detail=detail, | |
| request_id=getattr(request.state, "request_id", None) | |
| ) | |
| ) | |
| async def general_exception_handler(request: Request, exc: Exception): | |
| """ | |
| Handle all unhandled exceptions. | |
| """ | |
| logger.error( | |
| "Unhandled exception", | |
| extra={ | |
| "method": request.method, | |
| "path": request.url.path, | |
| "error": str(exc), | |
| "error_type": type(exc).__name__, | |
| "client_ip": request.client.host if request.client else None | |
| }, | |
| exc_info=True | |
| ) | |
| return JSONResponse( | |
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | |
| content=error_response( | |
| error="Internal Server Error", | |
| detail="An unexpected error occurred. Please try again later.", | |
| request_id=getattr(request.state, "request_id", None) | |
| ) | |
| ) | |
| # Startup and shutdown events | |
| async def startup_event(): | |
| """Initialize connections on startup""" | |
| logger.info("Starting POS Microservice") | |
| logger.info(f"DEBUG mode: {settings.DEBUG}") | |
| logger.info(f"JWT Algorithm: {settings.ALGORITHM}") | |
| logger.info(f"Token Expiration: {settings.TOKEN_EXPIRATION_HOURS} hours") | |
| await connect_to_mongo() | |
| await connect_to_database() | |
| logger.info("POS Microservice started successfully") | |
| async def shutdown_event(): | |
| """Close connections on shutdown""" | |
| logger.info("Shutting down POS Microservice") | |
| await close_mongo_connection() | |
| await disconnect_from_database() | |
| logger.info("POS Microservice shut down successfully") | |
| # Health check endpoint | |
| async def health_check(): | |
| """Health check endpoint""" | |
| return { | |
| "status": "healthy", | |
| "service": "pos-microservice", | |
| "version": "1.0.0" | |
| } | |
| # Token debug endpoint (for troubleshooting) | |
| async def debug_verify_token(token: str): | |
| """Debug endpoint to verify token format and claims (requires valid token for other endpoints)""" | |
| from app.dependencies.auth import verify_token | |
| result = { | |
| "token_length": len(token), | |
| "token_parts": len(token.split('.')), | |
| "valid": False, | |
| "payload": None, | |
| "error": None | |
| } | |
| if len(token.split('.')) != 3: | |
| result["error"] = "Token does not have 3 parts (invalid JWT format)" | |
| return result | |
| payload = verify_token(token) | |
| result["valid"] = payload is not None | |
| result["payload"] = payload | |
| if payload is None: | |
| result["error"] = "Token verification failed - check SECRET_KEY and signature" | |
| return result | |
| # Include routers | |
| # Authentication is handled by auth-ms, POS just validates tokens | |
| app.include_router(staff_router) | |
| app.include_router(catalogue_services_router) | |
| app.include_router(customers_router) | |
| app.include_router(sales_router) | |
| app.include_router(appointments_router) | |
| # Import and include wallet router | |
| from app.wallet.controllers.wallet_router import router as wallet_router | |
| app.include_router(wallet_router) | |
| # TODO: Add other POS-specific routers as they are implemented: | |
| # app.include_router(sales_router, prefix="/api/v1") | |
| # app.include_router(inventory_router, prefix="/api/v1") | |
| # app.include_router(customer_router, prefix="/api/v1") | |
| # app.include_router(product_router, prefix="/api/v1") | |
| # app.include_router(payment_router, prefix="/api/v1") | |
| # app.include_router(report_router, prefix="/api/v1"r) | |
| # app.include_router(payment_router) | |
| # app.include_router(report_router) | |
| if __name__ == "__main__": | |
| import uvicorn | |
| uvicorn.run( | |
| "app.main:app", | |
| host="0.0.0.0", | |
| port=8002, | |
| reload=True, | |
| log_level="info" | |
| ) | |