mirror of
https://github.com/Routstr/routstr-core.git
synced 2026-10-05 20:28:23 +00:00
feat(core): initialize and use SettingsService; update admin settings API; hook app metadata from settings
This commit is contained in:
+66
-7
@@ -9,7 +9,6 @@ from pydantic import BaseModel
|
||||
from sqlmodel import select
|
||||
|
||||
from ..wallet import (
|
||||
TRUSTED_MINTS,
|
||||
fetch_all_balances,
|
||||
get_proofs_per_mint_and_unit,
|
||||
get_wallet,
|
||||
@@ -18,12 +17,50 @@ from ..wallet import (
|
||||
)
|
||||
from .db import ApiKey, create_session
|
||||
from .logging import get_logger
|
||||
from .settings import SettingsService, settings
|
||||
|
||||
logger = get_logger(__name__)
|
||||
|
||||
admin_router = APIRouter(prefix="/admin", include_in_schema=False)
|
||||
|
||||
|
||||
@admin_router.get("/api/settings")
|
||||
async def get_settings(request: Request) -> dict:
|
||||
admin_cookie = request.cookies.get("admin_password")
|
||||
if not admin_cookie or admin_cookie != settings.admin_password:
|
||||
raise HTTPException(status_code=403, detail="Unauthorized")
|
||||
data = settings.dict()
|
||||
if "upstream_api_key" in data:
|
||||
data["upstream_api_key"] = "[REDACTED]" if data["upstream_api_key"] else ""
|
||||
if "admin_password" in data:
|
||||
data["admin_password"] = "[REDACTED]" if data["admin_password"] else ""
|
||||
if "nsec" in data:
|
||||
data["nsec"] = "[REDACTED]" if data["nsec"] else ""
|
||||
return data
|
||||
|
||||
|
||||
class SettingsUpdate(BaseModel):
|
||||
__root__: dict[str, object]
|
||||
|
||||
|
||||
@admin_router.patch("/api/settings")
|
||||
async def update_settings(request: Request, update: SettingsUpdate) -> dict:
|
||||
admin_cookie = request.cookies.get("admin_password")
|
||||
if not admin_cookie or admin_cookie != settings.admin_password:
|
||||
raise HTTPException(status_code=403, detail="Unauthorized")
|
||||
|
||||
async with create_session() as session:
|
||||
new_settings = await SettingsService.update(update.__root__, session)
|
||||
data = new_settings.dict()
|
||||
if "upstream_api_key" in data:
|
||||
data["upstream_api_key"] = "[REDACTED]" if data["upstream_api_key"] else ""
|
||||
if "admin_password" in data:
|
||||
data["admin_password"] = "[REDACTED]" if data["admin_password"] else ""
|
||||
if "nsec" in data:
|
||||
data["nsec"] = "[REDACTED]" if data["nsec"] else ""
|
||||
return data
|
||||
|
||||
|
||||
class WithdrawRequest(BaseModel):
|
||||
amount: int
|
||||
mint_url: str | None = None
|
||||
@@ -87,7 +124,12 @@ def info(content: str) -> str:
|
||||
|
||||
|
||||
def admin_auth() -> str:
|
||||
if os.getenv("ADMIN_PASSWORD", "") == "":
|
||||
try:
|
||||
settings = SettingsService.get()
|
||||
admin_pw = settings.admin_password
|
||||
except Exception:
|
||||
admin_pw = os.getenv("ADMIN_PASSWORD", "")
|
||||
if admin_pw == "":
|
||||
return info("Please set a secure ADMIN_PASSWORD= in your ENV variables.")
|
||||
else:
|
||||
return login_form()
|
||||
@@ -454,7 +496,12 @@ async def dashboard(request: Request) -> str:
|
||||
@admin_router.get("/", response_class=HTMLResponse)
|
||||
async def admin(request: Request) -> str:
|
||||
admin_cookie = request.cookies.get("admin_password")
|
||||
if admin_cookie and admin_cookie == os.getenv("ADMIN_PASSWORD"):
|
||||
try:
|
||||
settings = SettingsService.get()
|
||||
admin_pw: str = settings.admin_password
|
||||
except Exception:
|
||||
admin_pw = os.getenv("ADMIN_PASSWORD", "") or ""
|
||||
if admin_cookie and admin_cookie == admin_pw:
|
||||
return await dashboard(request)
|
||||
return admin_auth()
|
||||
|
||||
@@ -462,7 +509,12 @@ async def admin(request: Request) -> str:
|
||||
@admin_router.get("/logs/{request_id}", response_class=HTMLResponse)
|
||||
async def view_logs(request: Request, request_id: str) -> str:
|
||||
admin_cookie = request.cookies.get("admin_password")
|
||||
if not admin_cookie or admin_cookie != os.getenv("ADMIN_PASSWORD"):
|
||||
try:
|
||||
settings = SettingsService.get()
|
||||
admin_pw: str = settings.admin_password
|
||||
except Exception:
|
||||
admin_pw = os.getenv("ADMIN_PASSWORD", "") or ""
|
||||
if not admin_cookie or admin_cookie != admin_pw:
|
||||
return admin_auth()
|
||||
|
||||
logger.info(f"Investigating logs for request_id: {request_id}")
|
||||
@@ -660,16 +712,23 @@ async def withdraw(
|
||||
request: Request, withdraw_request: WithdrawRequest
|
||||
) -> dict[str, str]:
|
||||
admin_cookie = request.cookies.get("admin_password")
|
||||
if not admin_cookie or admin_cookie != os.getenv("ADMIN_PASSWORD"):
|
||||
try:
|
||||
settings = SettingsService.get()
|
||||
admin_pw: str = settings.admin_password
|
||||
except Exception:
|
||||
admin_pw = os.getenv("ADMIN_PASSWORD", "") or ""
|
||||
if not admin_cookie or admin_cookie != admin_pw:
|
||||
raise HTTPException(status_code=403, detail="Unauthorized")
|
||||
|
||||
# Get wallet and check balance
|
||||
from .settings import settings as global_settings
|
||||
|
||||
wallet = await get_wallet(
|
||||
withdraw_request.mint_url or TRUSTED_MINTS[0], withdraw_request.unit
|
||||
withdraw_request.mint_url or global_settings.primary_mint, withdraw_request.unit
|
||||
)
|
||||
proofs = get_proofs_per_mint_and_unit(
|
||||
wallet,
|
||||
withdraw_request.mint_url or TRUSTED_MINTS[0],
|
||||
withdraw_request.mint_url or global_settings.primary_mint,
|
||||
withdraw_request.unit,
|
||||
not_reserved=True,
|
||||
)
|
||||
|
||||
+16
-6
@@ -181,7 +181,12 @@ class SecurityFilter(logging.Filter):
|
||||
|
||||
def get_log_level() -> str:
|
||||
"""Get log level from environment variable."""
|
||||
level = os.environ.get("LOG_LEVEL", "INFO").upper()
|
||||
try:
|
||||
from .settings import settings
|
||||
|
||||
level = settings.log_level.upper()
|
||||
except Exception:
|
||||
level = os.environ.get("LOG_LEVEL", "INFO").upper()
|
||||
# Validate log level - if invalid, default to INFO
|
||||
valid_levels = {"TRACE", "DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"}
|
||||
if level not in valid_levels:
|
||||
@@ -191,11 +196,16 @@ def get_log_level() -> str:
|
||||
|
||||
def should_enable_console_logging() -> bool:
|
||||
"""Check if console logging should be enabled."""
|
||||
return os.environ.get("ENABLE_CONSOLE_LOGGING", "true").lower() in (
|
||||
"true",
|
||||
"1",
|
||||
"yes",
|
||||
)
|
||||
try:
|
||||
from .settings import settings
|
||||
|
||||
return bool(settings.enable_console_logging)
|
||||
except Exception:
|
||||
return os.environ.get("ENABLE_CONSOLE_LOGGING", "true").lower() in (
|
||||
"true",
|
||||
"1",
|
||||
"yes",
|
||||
)
|
||||
|
||||
|
||||
def setup_logging() -> None:
|
||||
|
||||
+24
-18
@@ -1,5 +1,4 @@
|
||||
import asyncio
|
||||
import os
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import AsyncGenerator
|
||||
|
||||
@@ -15,10 +14,12 @@ 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 .db import create_session, init_db, run_migrations
|
||||
from .exceptions import general_exception_handler, http_exception_handler
|
||||
from .logging import get_logger, setup_logging
|
||||
from .middleware import LoggingMiddleware
|
||||
from .settings import SettingsService
|
||||
from .settings import settings as global_settings
|
||||
|
||||
# Initialize logging first
|
||||
setup_logging()
|
||||
@@ -47,6 +48,17 @@ async def lifespan(_: FastAPI) -> AsyncGenerator[None, None]:
|
||||
# This creates any tables that might not be tracked by migrations yet
|
||||
await init_db()
|
||||
|
||||
# Initialize application settings (env -> computed -> DB precedence)
|
||||
async with create_session() as session:
|
||||
s = await SettingsService.initialize(session)
|
||||
|
||||
# Apply app metadata from settings
|
||||
try:
|
||||
app.title = s.name
|
||||
app.description = s.description
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
pricing_task = asyncio.create_task(update_sats_pricing())
|
||||
payout_task = asyncio.create_task(periodic_payout())
|
||||
nip91_task = asyncio.create_task(announce_provider())
|
||||
@@ -93,18 +105,12 @@ async def lifespan(_: FastAPI) -> AsyncGenerator[None, None]:
|
||||
)
|
||||
|
||||
|
||||
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,
|
||||
)
|
||||
app = FastAPI(version=__version__, lifespan=lifespan)
|
||||
|
||||
|
||||
# Configure CORS
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=os.environ.get("CORS_ORIGINS", "*").split(","),
|
||||
allow_origins=global_settings.cors_origins,
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
@@ -123,14 +129,14 @@ app.add_exception_handler(Exception, general_exception_handler)
|
||||
@app.get("/v1/info")
|
||||
async def info() -> dict:
|
||||
return {
|
||||
"name": app.title,
|
||||
"description": app.description,
|
||||
"name": global_settings.name,
|
||||
"description": global_settings.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,
|
||||
"npub": global_settings.npub,
|
||||
"mints": global_settings.cashu_mints,
|
||||
"http_url": global_settings.http_url,
|
||||
"onion_url": global_settings.onion_url,
|
||||
"models": MODELS, # todo maybe remove models from here
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user