"""FastAPI application entry point.""" import logging import os from contextlib import asynccontextmanager from dotenv import load_dotenv # Load .env file before anything else load_dotenv() from fastapi import FastAPI, Request, status from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from slowapi import Limiter, _rate_limit_exceeded_handler from slowapi.util import get_remote_address from slowapi.errors import RateLimitExceeded from app.api import api_router from app.config import get_settings from app.database import init_db from app.scheduler import start_scheduler, stop_scheduler from app.observability.metrics import instrument_app # Configure logging logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", ) logger = logging.getLogger(__name__) settings = get_settings() # Rate limiter configuration limiter = Limiter( key_func=get_remote_address, default_limits=["200/minute"], # Global default storage_uri=settings.rate_limit_storage_uri, # Use Redis in production ) @asynccontextmanager async def lifespan(app: FastAPI): """Application lifespan handler.""" # Startup logger.info(f"Starting {settings.app_name}...") # Initialize database await init_db() logger.info("Database initialized") # Start scheduler (optional - recommended: run in separate process/container) if settings.enable_scheduler: start_scheduler() logger.info("Scheduler started") else: logger.info("Scheduler disabled (ENABLE_SCHEDULER=false)") yield # Shutdown if settings.enable_scheduler: stop_scheduler() logger.info("Application shutdown complete") # Create FastAPI application app = FastAPI( title=settings.app_name, description=""" # pounce API Domain availability monitoring and portfolio management service. ## Features - **Domain Monitoring**: Track domains and get notified when they become available - **TLD Pricing**: Real-time TLD price comparison across registrars - **Portfolio Management**: Track your domain investments and valuations - **Smart Pounce Auctions**: Find undervalued domains in auctions ## Authentication Most endpoints require authentication via HttpOnly session cookie (recommended). Login: POST /api/v1/auth/login ## Rate Limits - Default: 200 requests/minute per IP - Auth endpoints: 10 requests/minute - Contact form: 5 requests/hour ## Support For API issues, contact support@pounce.ch """, version="1.0.0", lifespan=lifespan, redirect_slashes=False, docs_url="/docs", redoc_url="/redoc", ) # Observability (Prometheus metrics) if settings.enable_metrics: instrument_app(app, metrics_path=settings.metrics_path, enable_db_metrics=settings.enable_db_query_metrics) # Add rate limiter to app state app.state.limiter = limiter # Custom rate limit exceeded handler @app.exception_handler(RateLimitExceeded) async def rate_limit_handler(request: Request, exc: RateLimitExceeded): return JSONResponse( status_code=status.HTTP_429_TOO_MANY_REQUESTS, content={ "error": "rate_limit_exceeded", "detail": "Too many requests. Please slow down.", "retry_after": exc.detail, }, ) # Get allowed origins (env overrides settings) origins_raw = ( os.getenv("ALLOWED_ORIGINS", "").strip() or os.getenv("CORS_ORIGINS", "").strip() or (settings.cors_origins or "").strip() ) ALLOWED_ORIGINS = [o.strip() for o in origins_raw.split(",") if o.strip()] if not ALLOWED_ORIGINS: ALLOWED_ORIGINS = ["http://localhost:3000", "http://127.0.0.1:3000"] # Add production origins SITE_URL = os.getenv("SITE_URL", "") if SITE_URL and SITE_URL not in ALLOWED_ORIGINS: ALLOWED_ORIGINS.append(SITE_URL) # Configure CORS app.add_middleware( CORSMiddleware, allow_origins=ALLOWED_ORIGINS, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Include API routes app.include_router(api_router, prefix="/api/v1") @app.get("/") async def root(): """Root endpoint - API info.""" return { "name": settings.app_name, "version": "1.0.0", "status": "running", "docs": "/docs", "health": "/health", } @app.get("/health") async def health_check(): """Health check endpoint for monitoring.""" return { "status": "healthy", "service": settings.app_name, "version": "1.0.0", } # Rate-limited endpoints - apply specific limits to sensitive routes from fastapi import Depends @app.middleware("http") async def add_rate_limit_headers(request: Request, call_next): """Add rate limit info to response headers.""" response = await call_next(request) # Add CORS headers for rate limit info response.headers["X-RateLimit-Policy"] = "200/minute" return response