mitlist/be/app/main.py
Mohamad 8b3c784e54 feat: Enhance application configuration and logging with new features
This commit introduces several improvements to the application configuration and logging mechanisms, including:

- Added a new `REDIS_URL` configuration option in the production environment template for easier Redis setup.
- Implemented a soft delete method in the `UserManager` class to anonymize user data while maintaining referential integrity.
- Enhanced session secret management to ensure a secure fallback in non-production environments.
- Introduced a `PiiRedactionFilter` to loggers for redacting sensitive information from logs.
- Added rate limiting middleware to control API request rates and prevent abuse.

These changes aim to improve security, maintainability, and user data protection within the application.
2025-06-27 11:55:29 +02:00

129 lines
4.2 KiB
Python

import logging
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from starlette.middleware.sessions import SessionMiddleware
import sentry_sdk
from sentry_sdk.integrations.fastapi import FastApiIntegration
import os
import sys
from app.api.api_router import api_router
from app.config import settings
from app.core.api_config import API_METADATA, API_TAGS
from app.auth import fastapi_users, auth_backend
from app.schemas.user import UserPublic, UserCreate, UserUpdate
from app.core.scheduler import init_scheduler, shutdown_scheduler
from app.core.middleware import RequestContextMiddleware
from app.core.logging_utils import PiiRedactionFilter
from app.core.error_handlers import sqlalchemy_exception_handler, generic_exception_handler
from app.core.rate_limiter import RateLimitMiddleware
if settings.SENTRY_DSN:
sentry_sdk.init(
dsn=settings.SENTRY_DSN,
integrations=[
FastApiIntegration(),
],
traces_sample_rate=0.1 if settings.is_production else 1.0,
environment=settings.ENVIRONMENT,
send_default_pii=not settings.is_production
)
logging.basicConfig(
level=getattr(logging, settings.LOG_LEVEL),
format=settings.LOG_FORMAT
)
# Attach PII redaction filter to root logger
root_logger = logging.getLogger()
root_logger.addFilter(PiiRedactionFilter())
logger = logging.getLogger(__name__)
api_metadata = {
**API_METADATA,
"docs_url": settings.docs_url,
"redoc_url": settings.redoc_url,
"openapi_url": settings.openapi_url,
}
app = FastAPI(
**api_metadata,
openapi_tags=API_TAGS
)
app.add_middleware(
SessionMiddleware,
secret_key=settings.SESSION_SECRET_KEY
)
# Structured logging & request tracing
app.add_middleware(RequestContextMiddleware)
app.add_middleware(RateLimitMiddleware)
app.add_middleware(
CORSMiddleware,
allow_origins=(settings.cors_origins_list if not settings.is_production else [settings.FRONTEND_URL]),
# Credentials (cookies) are not required because we use JWTs in Authorization headers.
allow_credentials=False,
allow_methods=["*"],
allow_headers=["*"],
expose_headers=["*"]
)
# Register exception handlers BEFORE adding middleware/router
app.add_exception_handler(Exception, generic_exception_handler)
from sqlalchemy.exc import SQLAlchemyError
app.add_exception_handler(SQLAlchemyError, sqlalchemy_exception_handler)
app.include_router(api_router, prefix=settings.API_PREFIX)
@app.get("/health", tags=["Health"])
async def health_check():
"""Minimal health check endpoint that avoids leaking build metadata."""
return {"status": settings.HEALTH_STATUS_OK}
@app.get("/", tags=["Root"])
async def read_root():
"""Public root endpoint with minimal information."""
logger.info("Root endpoint '/' accessed.")
return {"message": settings.ROOT_MESSAGE}
async def run_migrations():
"""Run database migrations."""
try:
logger.info("Running database migrations...")
base_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
alembic_path = os.path.join(base_path, 'alembic')
if alembic_path not in sys.path:
sys.path.insert(0, alembic_path)
from migrations import run_migrations as run_db_migrations
await run_db_migrations()
logger.info("Database migrations completed successfully.")
except Exception as e:
logger.error(f"Error running migrations: {e}")
raise
@app.on_event("startup")
async def startup_event():
"""Initialize services on startup."""
logger.info(f"Application startup in {settings.ENVIRONMENT} environment...")
# await run_migrations()
init_scheduler()
logger.info("Application startup complete.")
@app.on_event("shutdown")
async def shutdown_event():
"""Cleanup services on shutdown."""
logger.info("Application shutdown: Disconnecting from database...")
shutdown_scheduler()
# Close Redis connection pool to avoid leaking file descriptors.
try:
from app.core.redis import redis_pool
await redis_pool.aclose()
logger.info("Redis pool closed.")
except Exception as e:
logger.warning(f"Error closing Redis pool: {e}")
logger.info("Application shutdown complete.")