107 lines
3.6 KiB
Python
107 lines
3.6 KiB
Python
"""FastAPI application entry point with lifespan management."""
|
|
|
|
import logging
|
|
import sys
|
|
from contextlib import asynccontextmanager
|
|
from collections.abc import AsyncGenerator
|
|
|
|
from fastapi import FastAPI
|
|
from passlib.hash import bcrypt
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.config import settings
|
|
from app.database import async_session_factory, engine
|
|
from app.middleware import register_exception_handlers
|
|
from app.models.user import User
|
|
from app.scheduler import configure_scheduler, scheduler
|
|
from app.routers.admin import router as admin_router
|
|
from app.routers.auth import router as auth_router
|
|
from app.routers.health import router as health_router
|
|
from app.routers.ingestion import router as ingestion_router
|
|
from app.routers.ohlcv import router as ohlcv_router
|
|
from app.routers.indicators import router as indicators_router
|
|
from app.routers.fundamentals import router as fundamentals_router
|
|
from app.routers.scores import router as scores_router
|
|
from app.routers.trades import router as trades_router
|
|
from app.routers.watchlist import router as watchlist_router
|
|
from app.routers.sentiment import router as sentiment_router
|
|
from app.routers.sr_levels import router as sr_levels_router
|
|
from app.routers.tickers import router as tickers_router
|
|
|
|
|
|
def _configure_logging() -> None:
|
|
"""Set up structured JSON-style logging."""
|
|
handler = logging.StreamHandler(sys.stdout)
|
|
handler.setFormatter(
|
|
logging.Formatter(
|
|
'{"time":"%(asctime)s","level":"%(levelname)s",'
|
|
'"logger":"%(name)s","message":"%(message)s"}'
|
|
)
|
|
)
|
|
root = logging.getLogger()
|
|
root.handlers.clear()
|
|
root.addHandler(handler)
|
|
root.setLevel(settings.log_level.upper())
|
|
|
|
|
|
async def _create_default_admin(session: AsyncSession) -> None:
|
|
"""Create the default admin account if no admin user exists."""
|
|
result = await session.execute(
|
|
select(User).where(User.role == "admin")
|
|
)
|
|
if result.scalar_one_or_none() is None:
|
|
admin = User(
|
|
username="admin",
|
|
password_hash=bcrypt.hash("admin"),
|
|
role="admin",
|
|
has_access=True,
|
|
)
|
|
session.add(admin)
|
|
await session.commit()
|
|
logging.getLogger(__name__).info("Default admin account created")
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(_app: FastAPI) -> AsyncGenerator[None, None]:
|
|
"""Manage startup and shutdown lifecycle."""
|
|
logger = logging.getLogger(__name__)
|
|
_configure_logging()
|
|
logger.info("Starting Stock Data Backend")
|
|
|
|
async with async_session_factory() as session:
|
|
await _create_default_admin(session)
|
|
|
|
configure_scheduler()
|
|
scheduler.start()
|
|
logger.info("Scheduler started")
|
|
|
|
yield
|
|
|
|
scheduler.shutdown(wait=False)
|
|
logger.info("Scheduler stopped")
|
|
await engine.dispose()
|
|
logger.info("Shutting down")
|
|
|
|
|
|
app = FastAPI(
|
|
title="Stock Data Backend",
|
|
version="0.1.0",
|
|
lifespan=lifespan,
|
|
)
|
|
|
|
register_exception_handlers(app)
|
|
app.include_router(health_router, prefix="/api/v1")
|
|
app.include_router(auth_router, prefix="/api/v1")
|
|
app.include_router(admin_router, prefix="/api/v1")
|
|
app.include_router(tickers_router, prefix="/api/v1")
|
|
app.include_router(ohlcv_router, prefix="/api/v1")
|
|
app.include_router(ingestion_router, prefix="/api/v1")
|
|
app.include_router(indicators_router, prefix="/api/v1")
|
|
app.include_router(sr_levels_router, prefix="/api/v1")
|
|
app.include_router(sentiment_router, prefix="/api/v1")
|
|
app.include_router(fundamentals_router, prefix="/api/v1")
|
|
app.include_router(scores_router, prefix="/api/v1")
|
|
app.include_router(trades_router, prefix="/api/v1")
|
|
app.include_router(watchlist_router, prefix="/api/v1")
|