from contextlib import asynccontextmanager from fastapi import FastAPI, Request from fastapi.exceptions import RequestValidationError from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from app.config import get_settings from app.database import get_pool, close_pool from app.exceptions import AppError from app.routers import auth, products, orders, bookings, payments, services, contact from app.routers.admin import dashboard, products as admin_products, orders as admin_orders from app.routers.admin import bookings as admin_bookings, customers, settings as admin_settings from app.routers.admin import services as admin_services @asynccontextmanager async def lifespan(app: FastAPI): await get_pool() # warm up the pool on startup yield await close_pool() def create_app() -> FastAPI: cfg = get_settings() app = FastAPI( title="Bado Hair API", version="1.0.0", docs_url="/docs" if not cfg.is_production else None, redoc_url="/redoc" if not cfg.is_production else None, lifespan=lifespan, ) # ── CORS ────────────────────────────────────────────────────────────────── app.add_middleware( CORSMiddleware, allow_origins=cfg.CORS_ORIGINS, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # ── Error handlers ──────────────────────────────────────────────────────── @app.exception_handler(AppError) async def app_error_handler(request: Request, exc: AppError): return JSONResponse( status_code=exc.status_code, content={ "success": False, "error": { "code": exc.code, "message": exc.message, "details": exc.details, }, }, ) @app.exception_handler(RequestValidationError) async def validation_error_handler(request: Request, exc: RequestValidationError): return JSONResponse( status_code=422, content={ "success": False, "error": { "code": "VALIDATION_ERROR", "message": "Request validation failed", "details": exc.errors(), }, }, ) @app.exception_handler(Exception) async def unhandled_error_handler(request: Request, exc: Exception): import traceback if not cfg.is_production: traceback.print_exc() return JSONResponse( status_code=500, content={ "success": False, "error": { "code": "INTERNAL_ERROR", "message": "An unexpected error occurred", "details": None, }, }, ) # ── Customer routes (/api/v1) ───────────────────────────────────────────── v1 = "/api/v1" app.include_router(auth.router, prefix=v1) app.include_router(products.router, prefix=v1) app.include_router(orders.router, prefix=v1) app.include_router(bookings.router, prefix=v1) app.include_router(payments.router, prefix=v1) app.include_router(services.router, prefix=v1) app.include_router(contact.router, prefix=v1) # ── Admin routes (/api/v1/admin) ────────────────────────────────────────── adm = f"{v1}/admin" app.include_router(dashboard.router, prefix=adm) app.include_router(admin_products.router, prefix=adm) app.include_router(admin_orders.router, prefix=adm) app.include_router(admin_bookings.router, prefix=adm) app.include_router(customers.router, prefix=adm) app.include_router(admin_services.router, prefix=adm) app.include_router(admin_settings.router, prefix=adm) @app.get("/health") async def health(): return {"status": "ok"} return app app = create_app()