"""App-level settings API (admin only).""" import logging import os from fastapi import APIRouter, Depends from pydantic import BaseModel from sqlmodel import select from sqlmodel.ext.asyncio.session import AsyncSession from ..auth.dependencies import require_admin from ..database.engine import get_session from ..database.models import AppSetting, TelegramBot, User _LOGGER = logging.getLogger(__name__) router = APIRouter(prefix="/api/settings", tags=["settings"]) # Keys exposed to the frontend with their env-var fallbacks _SETTING_KEYS = { "external_url": "NOTIFY_BRIDGE_EXTERNAL_URL", "telegram_webhook_secret": "NOTIFY_BRIDGE_TELEGRAM_WEBHOOK_SECRET", "telegram_cache_ttl_hours": None, # no env fallback, default 48 } _DEFAULTS = { "external_url": "", "telegram_webhook_secret": "", "telegram_cache_ttl_hours": "48", } async def get_setting(session: AsyncSession, key: str) -> str: """Read a setting from DB, falling back to env var then default.""" row = await session.get(AppSetting, key) if row and row.value: return row.value env_key = _SETTING_KEYS.get(key) if env_key: env_val = os.environ.get(env_key, "") if env_val: return env_val return _DEFAULTS.get(key, "") class SettingsUpdate(BaseModel): external_url: str | None = None telegram_webhook_secret: str | None = None telegram_cache_ttl_hours: str | None = None @router.get("") async def get_settings( user: User = Depends(require_admin), session: AsyncSession = Depends(get_session), ): """Return all app settings.""" result = {} for key in _SETTING_KEYS: result[key] = await get_setting(session, key) return result @router.put("") async def update_settings( body: SettingsUpdate, user: User = Depends(require_admin), session: AsyncSession = Depends(get_session), ): """Update app settings (admin). Re-registers webhooks when base URL changes.""" old_base_url = await get_setting(session, "external_url") old_secret = await get_setting(session, "telegram_webhook_secret") for key in _SETTING_KEYS: value = getattr(body, key, None) if value is None: continue row = await session.get(AppSetting, key) if row: row.value = value else: row = AppSetting(key=key, value=value) session.add(row) await session.commit() new_base_url = await get_setting(session, "external_url") new_secret = await get_setting(session, "telegram_webhook_secret") # Update webhook secret in the webhook handler module if new_secret != old_secret: from ..commands.webhook import set_webhook_secret set_webhook_secret(new_secret or None) # Re-register webhooks for all bots in webhook mode if URL or secret changed if new_base_url and (new_base_url != old_base_url or new_secret != old_secret): await _reregister_webhooks(session, new_base_url, new_secret) result = {} for key in _SETTING_KEYS: result[key] = await get_setting(session, key) return result async def _reregister_webhooks( session: AsyncSession, base_url: str, secret: str ) -> None: """Re-register webhooks for all bots in webhook mode.""" from ..commands.webhook import register_webhook result = await session.exec( select(TelegramBot).where(TelegramBot.update_mode == "webhook") ) bots = result.all() for bot in bots: webhook_url = f"{base_url.rstrip('/')}/api/telegram/webhook/{bot.webhook_path_id}" res = await register_webhook(bot.token, webhook_url, secret or None) if res.get("success"): _LOGGER.info("Re-registered webhook for bot %d (%s)", bot.id, bot.name) else: _LOGGER.warning( "Failed to re-register webhook for bot %d: %s", bot.id, res.get("error"), )