from __future__ import annotations from datetime import timedelta from quart import Blueprint, current_app, jsonify, request from .auth import require_admin from .db import session_scope from .settings import get_admin_settings, refresh_live, set_settings, validate_updates bp = Blueprint("settings", __name__, url_prefix="/api/settings") @bp.get("") @require_admin async def list_settings(): async with session_scope() as db: return jsonify({"settings": await get_admin_settings(db)}) @bp.patch("") @require_admin async def update_settings(): raw = await request.get_json(silent=True) data = raw if isinstance(raw, dict) else {} # Accept either {settings: {...}} or a bare {key: value} object. updates = data.get("settings") if isinstance(data.get("settings"), dict) else data if not isinstance(updates, dict): return jsonify({"error": "expected an object of settings"}), 400 clean, error = validate_updates(updates) if error is not None: return jsonify({"error": error}), 400 async with session_scope() as db: await set_settings(db, clean) await db.commit() # Re-read the cached security values so a saved limit or hop count applies to # the very next request. Unconditional: cheap, and a conditional here would be # one more place that has to know which keys are hot. await refresh_live(db) result = await get_admin_settings(db) # Apply the live-tunable knob without a restart (rule 25). if "session_ttl_days" in clean: current_app.config["PERMANENT_SESSION_LIFETIME"] = timedelta(days=int(clean["session_ttl_days"])) return jsonify({"settings": result})