Files
routstr-core/router/core/main.py
T

118 lines
3.5 KiB
Python

import asyncio
import os
from contextlib import asynccontextmanager
from typing import AsyncGenerator
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from ..balance import balance_router, deprecated_wallet_router
from ..discovery import providers_router
from ..payment.models import MODELS, models_router, update_sats_pricing
from ..proxy import proxy_router
from ..wallet import periodic_payout
from .admin import admin_router
from .db import init_db, run_migrations
from .logging import get_logger, setup_logging
# Initialize logging first
setup_logging()
logger = get_logger(__name__)
__version__ = "0.1.0"
@asynccontextmanager
async def lifespan(_: FastAPI) -> AsyncGenerator[None, None]:
logger.info("Application startup initiated", extra={"version": __version__})
pricing_task = None
payout_task = None
try:
# Run database migrations on startup
# This ensures the database schema is always up-to-date in production
# Migrations are idempotent - running them multiple times is safe
logger.info("Running database migrations")
run_migrations()
# Initialize database connection pools
# This creates any tables that might not be tracked by migrations yet
await init_db()
pricing_task = asyncio.create_task(update_sats_pricing())
payout_task = asyncio.create_task(periodic_payout())
yield
except Exception as e:
logger.error(
"Application startup failed",
extra={"error": str(e), "error_type": type(e).__name__},
)
raise
finally:
logger.info("Application shutdown initiated")
if pricing_task is not None:
pricing_task.cancel()
if payout_task is not None:
payout_task.cancel()
try:
tasks_to_wait = []
if pricing_task is not None:
tasks_to_wait.append(pricing_task)
if payout_task is not None:
tasks_to_wait.append(payout_task)
if tasks_to_wait:
await asyncio.gather(*tasks_to_wait, return_exceptions=True)
logger.info("Background tasks stopped successfully")
except Exception as e:
logger.error(
"Error stopping background tasks",
extra={"error": str(e), "error_type": type(e).__name__},
)
app = FastAPI(
version=__version__,
title=os.environ.get("NAME", "ARoutstrNode" + __version__),
description=os.environ.get("DESCRIPTION", "A Routstr Node"),
contact={"name": os.environ.get("NAME", ""), "npub": os.environ.get("NPUB", "")},
lifespan=lifespan,
)
# Configure CORS
app.add_middleware(
CORSMiddleware,
allow_origins=os.environ.get("CORS_ORIGINS", "*").split(","),
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/", include_in_schema=False)
@app.get("/v1/info")
async def info() -> dict:
return {
"name": app.title,
"description": app.description,
"version": __version__,
"npub": os.environ.get("NPUB", ""),
"mints": os.environ.get("CASHU_MINTS", "").split(","),
"http_url": os.environ.get("HTTP_URL", ""),
"onion_url": os.environ.get("ONION_URL", ""),
"models": MODELS,
}
app.include_router(models_router)
app.include_router(admin_router)
app.include_router(balance_router)
app.include_router(deprecated_wallet_router)
app.include_router(providers_router)
app.include_router(proxy_router)