Files
plm-lottery/app/main.py
T
davideandClaude Sonnet 5 7e5c372833 Log to a file instead of only stdout
All app and uvicorn logging now goes to logs/app.log (rotating,
10MB x5), and a catch-all exception handler logs full tracebacks there
before returning a generic 500 — so an error is traceable to its cause
without depending on how the process was launched. logs/ is gitignored,
like the other runtime artifacts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-21 10:46:47 +02:00

87 lines
2.9 KiB
Python

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.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.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")