"""Trade-In MVP — FastAPI entry point. Standalone версия, выделена из основного gendesign repo для локальной разработки. Только trade-in фича; никаких других роутеров (concepts/parcels/analytics/etc) нет. """ from __future__ import annotations import asyncio import logging from collections.abc import AsyncGenerator from contextlib import asynccontextmanager import sentry_sdk from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from app.api.v1 import admin, brand, geocode, search, trade_in from app.core.config import settings from app.core.ratelimit import RateLimitMiddleware from app.services.scheduler import scheduler_loop logger = logging.getLogger(__name__) logging.basicConfig( level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s", ) # Мониторинг ошибок — GlitchTip (Sentry-совместимый, #396). # DSN из env GLITCHTIP_DSN; пусто (dev) → мониторинг не инициализируется. if settings.glitchtip_dsn: sentry_sdk.init( dsn=settings.glitchtip_dsn, environment=settings.environment, traces_sample_rate=0.0, # только ошибки, без performance-трейсов send_default_pii=False, # не шлём client_name / client_phone в отчёты ) logging.getLogger("app.main").info("GlitchTip monitoring enabled") @asynccontextmanager async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: # Startup: launch scheduler background task scheduler_task = asyncio.create_task(scheduler_loop()) logger.info("FastAPI lifespan: scheduler task spawned") yield # Shutdown: cancel scheduler scheduler_task.cancel() try: await scheduler_task except asyncio.CancelledError: pass logger.info("FastAPI lifespan: scheduler cancelled cleanly") app = FastAPI( title="Trade-In MVP API", description="Оценка вторичного жилья (выкупная стоимость) — копия trade-in feature из gendesign", # noqa: E501 version="0.1.0", lifespan=lifespan, ) app.add_middleware( CORSMiddleware, allow_origins=settings.cors_origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Rate-limit публичного API (per-IP sliding window) — защита от абуза. app.add_middleware(RateLimitMiddleware) @app.get("/health") def health() -> dict[str, str]: return {"status": "ok", "environment": settings.environment} app.include_router(geocode.router, prefix="/api/v1/geocode", tags=["geocode"]) app.include_router(admin.router, prefix="/api/v1/admin", tags=["admin"]) app.include_router(brand.router, prefix="/api/v1/brand", tags=["brand"]) app.include_router(trade_in.router, prefix="/api/v1/trade-in", tags=["trade-in"]) app.include_router(search.router, prefix="/api/v1", tags=["search"])