MyMidas/backend/app/main.py
megaproxy 664b530136 Seed historical net worth snapshots and trigger live price sync on demo startup
- seed.py: adds 30 weekly NetWorthSnapshot rows (Sep 2025 → Apr 2026) so the
  Net Worth chart has full history on first boot, not just today's value
- main.py: fires price_sync_job and fx_sync_job in the background immediately
  after the scheduler starts in demo mode, so portfolio valuations are live
  from the moment the container becomes healthy

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 22:47:44 +00:00

113 lines
3.3 KiB
Python

"""
FastAPI application factory with lifespan management.
"""
from contextlib import asynccontextmanager
import structlog
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from redis.asyncio import Redis, from_url
from app.config import get_settings
from app.core.middleware import CSRFMiddleware, SecurityHeadersMiddleware
from app.db.base import create_engine, create_session_factory
from app.dependencies import set_redis_client, set_session_factory
logger = structlog.get_logger()
@asynccontextmanager
async def lifespan(app: FastAPI):
settings = get_settings()
# Database
engine = create_engine()
session_factory = create_session_factory(engine)
set_session_factory(session_factory)
# Redis
redis: Redis = from_url(settings.redis_url, decode_responses=False)
set_redis_client(redis)
# Seed system categories if needed
from app.services.category_service import seed_system_categories
async with session_factory() as db:
await seed_system_categories(db)
await db.commit()
# Demo mode: seed demo data on first startup, then snapshot
if settings.is_demo:
from app.demo.seed import is_seeded, seed_demo
from app.demo.snapshot import create_snapshot
async with session_factory() as db:
if not await is_seeded(db):
await seed_demo(db)
await db.commit()
logger.info("demo_seed_complete")
await create_snapshot()
logger.info("demo_snapshot_created")
# Background scheduler
from app.workers.scheduler import start_scheduler, stop_scheduler
await start_scheduler()
# Demo: sync live prices and FX rates immediately so portfolio values are fresh
if settings.is_demo:
import asyncio
from app.workers.price_sync import price_sync_job
from app.workers.fx_sync import fx_sync_job
asyncio.ensure_future(price_sync_job())
asyncio.ensure_future(fx_sync_job())
logger.info("demo_background_sync_queued")
logger.info("startup_complete", env=settings.environment)
yield
await stop_scheduler()
await redis.aclose()
await engine.dispose()
logger.info("shutdown_complete")
def create_app() -> FastAPI:
settings = get_settings()
app = FastAPI(
title="Finance Tracker",
version="0.1.0",
docs_url="/docs" if settings.is_development else None,
redoc_url="/redoc" if settings.is_development else None,
openapi_url="/openapi.json" if settings.is_development else None,
lifespan=lifespan,
)
# CORS — only allow same origin in production
origins = ["http://localhost:5173"] if settings.is_development else []
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app.add_middleware(SecurityHeadersMiddleware)
app.add_middleware(CSRFMiddleware)
# Health check (no auth required)
@app.get("/health")
async def health():
return {"status": "ok"}
@app.get("/demo/status")
async def demo_status():
return {"demo_mode": settings.is_demo}
# API routers
from app.api.router import router
app.include_router(router, prefix="/api/v1")
return app
app = create_app()