mirror of
https://github.com/Routstr/routstr-core.git
synced 2026-08-10 11:13:08 +00:00
142 lines
4.3 KiB
Python
142 lines
4.3 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 fastapi.responses import RedirectResponse
|
|
from starlette.exceptions import HTTPException
|
|
|
|
from ..balance import balance_router, deprecated_wallet_router
|
|
from ..discovery import providers_router
|
|
from ..nip91 import announce_provider
|
|
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 .exceptions import general_exception_handler, http_exception_handler
|
|
from .logging import get_logger, setup_logging
|
|
from .middleware import LoggingMiddleware
|
|
|
|
# Initialize logging first
|
|
setup_logging()
|
|
logger = get_logger(__name__)
|
|
|
|
__version__ = "0.1.1b"
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(_: FastAPI) -> AsyncGenerator[None, None]:
|
|
logger.info("Application startup initiated", extra={"version": __version__})
|
|
|
|
pricing_task = None
|
|
payout_task = None
|
|
nip91_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())
|
|
nip91_task = asyncio.create_task(announce_provider())
|
|
|
|
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()
|
|
if nip91_task is not None:
|
|
nip91_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 nip91_task is not None:
|
|
tasks_to_wait.append(nip91_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=["*"],
|
|
expose_headers=["x-routstr-request-id"],
|
|
)
|
|
|
|
# Add logging middleware
|
|
app.add_middleware(LoggingMiddleware)
|
|
|
|
# Add exception handlers
|
|
app.add_exception_handler(HTTPException, http_exception_handler) # type: ignore
|
|
app.add_exception_handler(Exception, general_exception_handler)
|
|
|
|
|
|
@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.get("/admin")
|
|
async def admin_redirect() -> RedirectResponse:
|
|
return RedirectResponse("/admin/")
|
|
|
|
|
|
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)
|