""" Apl_GS API — FastAPI Application Entry Point Run: python app.py Or directly with uvicorn: uvicorn app:app --host 0.0.0.0 --port 5000 --reload API Docs: http://localhost:5000/docs (Swagger UI) http://localhost:5000/redoc (ReDoc) Architecture Reference: insights/architecture_rule.md """ import os import logging from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from dotenv import load_dotenv # Load environment variables FIRST (before any config imports) load_dotenv() from web.route import router from config.rate_limiter import rate_limit_middleware from config.database_config import init_db, close_db # ── Logging ── logging.basicConfig( level=os.getenv("LOG_LEVEL", "INFO"), format="%(asctime)s — %(name)s — %(levelname)s — %(message)s", ) logger = logging.getLogger(__name__) # ── App Metadata ── APP_NAME = os.getenv("APP_NAME", "Apl_GS API") APP_VERSION = "1.0.0" APP_ENV = os.getenv("APP_ENV", "development") APP_DEBUG = os.getenv("APP_DEBUG", "False").lower() == "true" # ── Lifespan (startup + shutdown) ── @asynccontextmanager async def lifespan(app: FastAPI): """Application lifecycle — runs on startup and shutdown.""" # ── STARTUP ── logger.info(f"🚀 Starting {APP_NAME} v{APP_VERSION}") logger.info(f"📍 Environment: {APP_ENV}") logger.info(f"🔧 Debug mode: {APP_DEBUG}") logger.info(f"🌐 Port: {os.getenv('PORT', '5000')}") # Initialize database tables try: await init_db() logger.info("✅ Database initialized successfully") except Exception as e: logger.error(f"❌ Database initialization failed: {e}") logger.warning("⚠️ API will start but database operations will fail") logger.warning("⚠️ Make sure PostgreSQL 15+ is installed and running!") yield # Application runs here # ── SHUTDOWN ── logger.info("🛑 Shutting down Apl_GS API") await close_db() # ── Create FastAPI Instance ── app = FastAPI( title=APP_NAME, version=APP_VERSION, description=( "Apl_GS Backend API — Visit & Checklist Management System.\n\n" "**No authentication required** — protected by rate limiter.\n\n" "Built with FastAPI + PostgreSQL 15+.\n\n" "📖 Architecture Reference: `insights/architecture_rule.md`" ), docs_url="/docs", redoc_url="/redoc", openapi_url="/openapi.json", lifespan=lifespan, ) # ── CORS Middleware ── cors_origins = os.getenv( "CORS_ORIGINS", "http://localhost" ).split(",") app.add_middleware( CORSMiddleware, allow_origins=[origin.strip() for origin in cors_origins], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # ── Rate Limiter Middleware ── app.middleware("http")(rate_limit_middleware) # ── Include API Routes ── app.include_router(router) # ── Root Endpoint ── @app.get("/", tags=["Root"], summary="API root — basic info and links") async def root(): """ Root endpoint — returns API info, version, and documentation links. """ return { "status": "success", "data": { "name": APP_NAME, "version": APP_VERSION, "environment": APP_ENV, "docs": "/docs", "redoc": "/redoc", "health": "/api/v1/health", }, "message": "Apl_GS API is running", } # ── Global Exception Handler ── @app.exception_handler(Exception) async def global_exception_handler(request, exc): """Handle unexpected exceptions gracefully.""" logger.error(f"Unhandled exception: {exc}", exc_info=True) return JSONResponse( status_code=500, content={ "status": "error", "error": { "code": "INTERNAL_ERROR", "message": "An unexpected error occurred", "details": str(exc) if APP_DEBUG else None, }, }, ) # ── Run with Uvicorn ── if __name__ == "__main__": import uvicorn host = os.getenv("HOST", "0.0.0.0") port = int(os.getenv("PORT", "5000")) logger.info(f"🌟 Running on http://{host}:{port}") logger.info(f"📚 Swagger UI: http://{host}:{port}/docs") logger.info(f"📚 ReDoc: http://{host}:{port}/redoc") uvicorn.run( "app:app", host=host, port=port, reload=(APP_ENV == "development"), log_level=os.getenv("LOG_LEVEL", "info").lower(), )