feat: telegram commands, app settings, bot polling, webhook handling, UI improvements
Adds telegram bot command system with 13 commands (search, latest, random, etc.), webhook/polling handlers, rate limiting, app settings page, and various UI/UX improvements across all entity pages. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
"""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 get_current_user
|
||||
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(get_current_user),
|
||||
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(get_current_user),
|
||||
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"),
|
||||
)
|
||||
Reference in New Issue
Block a user