import asyncio import logging from contextlib import asynccontextmanager from fastapi import FastAPI, Request from fastapi.responses import FileResponse, JSONResponse from fastapi.staticfiles import StaticFiles from app.logging_config import setup_logging setup_logging() import app.bets.confirmation # noqa: F401 (registers the "bet" confirmation handler) import app.rounds.confirmation # noqa: F401 (registers the "payout" confirmation handler) import app.withdrawals.confirmation # noqa: F401 (registers the "withdrawal" confirmation handler) from app.api.routes.admin import router as admin_router from app.api.routes.bets import router as bets_router from app.api.routes.qr import router as qr_router from app.api.routes.users import router as users_router from app.api.routes.withdrawals import router as withdrawals_router from app.auth.routes import router as auth_router from app.config import settings from app.db.base import AsyncSessionLocal from app.electrum.client import ElectrumClient from app.electrum.listener import ElectrumListener from app.rounds.scheduler import RoundScheduler from app.tx.broadcast import RbfBumper from app.tx.confirmation import ConfirmationPoller from app.tx.locks import UserLocks logger = logging.getLogger(__name__) def _make_electrum_client() -> ElectrumClient: return ElectrumClient(settings.electrum_host, settings.electrum_port, settings.electrum_use_ssl) @asynccontextmanager async def lifespan(app: FastAPI): listener = ElectrumListener(_make_electrum_client, AsyncSessionLocal) app.state.electrum_listener = listener app.state.user_locks = UserLocks() scheduler = RoundScheduler(AsyncSessionLocal, listener) poller = ConfirmationPoller(AsyncSessionLocal, lambda: listener.client) bumper = RbfBumper(AsyncSessionLocal, lambda: listener.client) tasks = [ asyncio.create_task(listener.run()), asyncio.create_task(scheduler.run()), asyncio.create_task(poller.run()), asyncio.create_task(bumper.run()), ] try: yield finally: for task in tasks: task.cancel() if listener.client is not None: await listener.client.close() app = FastAPI(title="PLM Lottery", lifespan=lifespan) app.include_router(auth_router) app.include_router(users_router) app.include_router(bets_router) app.include_router(withdrawals_router) app.include_router(admin_router) app.include_router(qr_router) @app.exception_handler(Exception) async def log_unhandled_exception(request: Request, exc: Exception) -> JSONResponse: logger.exception("Unhandled error on %s %s", request.method, request.url.path) return JSONResponse(status_code=500, content={"detail": "internal server error"}) @app.get("/health") async def health() -> dict[str, str]: return {"status": "ok"} @app.get("/admin", include_in_schema=False) async def admin_panel() -> FileResponse: return FileResponse("app/static/admin.html") app.mount("/", StaticFiles(directory="app/static", html=True), name="static")