Files
nutshell/cashu/mint/app.py
lollerfirst 29571287b3 Mint Management gRPC Server (#723)
* settings

* fix name settings

* management rpc

* hook up the RPC server

* working

* format

* update build script fix import error

* remove accidental commit of vscode extension data

* working ✔

* \n

* add get mint quote get melt quote

* gRPC cli update quotes commands

* update mint melt quotes from cli

* comment under get cli command group

* keyset rotation not yet implemented

* try fix

* change back contact info default to be empty list

* fix import

* add server mTLS

* ll

* script for generating certificates

* rename settings

* move generation script

* do not save TTL expiry into Cache object, rather always load from settings.

* update lightning fees

* update auth limits

* auth rate limit cli

* optional arguemnts

* better error messages

* tests for db update mint/melt quotes

* start mint rpc tests

* add tos_url field to get-info grpc response

* format checks

* add types to click groups where it's needed

* tests on updating quotes

* fix tests

* skip updating mint quote state if on regtest

* test edge case

* unified test_add_remove_contact

* mark pytest-asyncio

* fix missing db argument

* hopefully no more silly errors

* fix test_db_update_mint_quote_state

* pass in the quote id string.

* add keyset rotation

* test for keyset rotation through gRPC command

* fix logger warning

* remove rotation test because it breaks other tests

* use different bolt11 invoices

* assert returned melt quote has quote

* is_postgres

* try different things

* skip if deprecated api

* format checks

* update .gitignore

* default location for certificates
2025-06-25 12:35:53 +02:00

127 lines
3.7 KiB
Python

import asyncio
import sys
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from traceback import print_exception
from fastapi import FastAPI, status
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from loguru import logger
from starlette.requests import Request
from ..core.errors import CashuError
from ..core.logging import configure_logger
from ..core.settings import settings
from .auth.router import auth_router
from .router import redis, router
from .router_deprecated import router_deprecated
from .startup import (
shutdown_management_rpc,
shutdown_mint,
start_auth,
start_management_rpc,
start_mint,
)
if settings.debug_profiling:
pass
if settings.mint_rate_limit:
pass
from .middleware import add_middlewares, request_validation_exception_handler
@asynccontextmanager
async def lifespan(_: FastAPI) -> AsyncIterator[None]:
await start_mint()
if settings.mint_require_auth:
await start_auth()
if settings.mint_rpc_server_enable:
await start_management_rpc()
try:
yield
except asyncio.CancelledError:
# Handle the cancellation gracefully
logger.info("Shutdown process interrupted by CancelledError")
finally:
try:
await shutdown_management_rpc()
await redis.disconnect()
await shutdown_mint()
except asyncio.CancelledError:
logger.error("CancelledError during shutdown, shutting down forcefully")
def create_app(config_object="core.settings") -> FastAPI:
configure_logger()
app = FastAPI(
title="Nutshell Mint",
description="Ecash mint based on the Cashu protocol.",
version=settings.version,
license_info={
"name": "MIT License",
"url": "https://raw.githubusercontent.com/cashubtc/cashu/main/LICENSE",
},
lifespan=lifespan,
)
return app
app = create_app()
# Add middlewares
add_middlewares(app)
@app.middleware("http")
async def catch_exceptions(request: Request, call_next):
CORS_HEADERS = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "*",
"Access-Control-Allow-Headers": "*",
"Access-Control-Allow-Credentials": "true",
}
try:
return await call_next(request)
except Exception as e:
try:
err_message = str(e)
except Exception:
err_message = e.args[0] if e.args else "Unknown error"
if isinstance(e, CashuError) or isinstance(e.args[0], CashuError):
logger.error(f"CashuError: {err_message}")
code = e.code if isinstance(e, CashuError) else e.args[0].code
# return with cors headers
return JSONResponse(
status_code=status.HTTP_400_BAD_REQUEST,
content={"detail": err_message, "code": code},
headers=CORS_HEADERS,
)
logger.error(f"Exception: {err_message}")
if settings.debug:
print_exception(*sys.exc_info())
return JSONResponse(
status_code=status.HTTP_400_BAD_REQUEST,
content={"detail": err_message, "code": 0},
headers=CORS_HEADERS,
)
# Add exception handlers
app.add_exception_handler(RequestValidationError, request_validation_exception_handler) # type: ignore
# Add routers
if settings.debug_mint_only_deprecated:
app.include_router(router=router_deprecated, tags=["Deprecated"], deprecated=True)
else:
app.include_router(router=router, tags=["Mint"])
app.include_router(router=router_deprecated, tags=["Deprecated"], deprecated=True)
if settings.mint_require_auth:
app.include_router(auth_router, tags=["Auth"])