"""FastAPI application entry point.""" import asyncio import logging from contextlib import asynccontextmanager from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware from database import Base, async_session, engine from models import PriceHistory from price_fetcher import PAIR_BTC_EUR, SOURCE_BITFINEX, fetch_btc_eur_price from routes import audit as audit_routes from routes import auth as auth_routes from routes import availability as availability_routes from routes import exchange as exchange_routes from routes import invites as invites_routes from routes import meta as meta_routes from routes import pricing as pricing_routes from routes import profile as profile_routes from routes import test as test_routes from shared_constants import PRICE_REFRESH_SECONDS from validate_constants import validate_shared_constants logger = logging.getLogger(__name__) # Background task handle _price_fetch_task: asyncio.Task | None = None async def periodic_price_fetcher(): """Background task that fetches BTC/EUR price every minute.""" logger.info( "Starting periodic price fetcher (every %d seconds)", PRICE_REFRESH_SECONDS ) while True: try: price_value, timestamp = await fetch_btc_eur_price() async with async_session() as db: new_price = PriceHistory( source=SOURCE_BITFINEX, pair=PAIR_BTC_EUR, price=price_value, timestamp=timestamp, ) db.add(new_price) await db.commit() logger.info("Fetched BTC/EUR price: €%.2f", price_value) except Exception as e: logger.error("Failed to fetch price: %s", e) await asyncio.sleep(PRICE_REFRESH_SECONDS) @asynccontextmanager async def lifespan(app: FastAPI): """Create database tables on startup and validate constants.""" global _price_fetch_task # Validate shared constants match backend definitions validate_shared_constants() async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) # Start background price fetcher _price_fetch_task = asyncio.create_task(periodic_price_fetcher()) yield # Cancel background task on shutdown if _price_fetch_task: _price_fetch_task.cancel() try: await _price_fetch_task except asyncio.CancelledError: logger.info("Price fetcher task cancelled") app = FastAPI(lifespan=lifespan) app.add_middleware( CORSMiddleware, allow_origins=["http://localhost:3000"], allow_methods=["*"], allow_headers=["*"], allow_credentials=True, ) # Include routers - modules with single router app.include_router(auth_routes.router) app.include_router(audit_routes.router) app.include_router(profile_routes.router) app.include_router(availability_routes.router) app.include_router(pricing_routes.router) app.include_router(meta_routes.router) app.include_router(test_routes.router) # Include routers - modules with multiple routers for r in invites_routes.routers: app.include_router(r) for r in exchange_routes.routers: app.include_router(r)