pounce/backend/app/main.py

183 lines
4.8 KiB
Python

"""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