Files
notify-bridge/packages/server/src/notify_bridge_server/api/webhook_logs.py
T
alexei.dolgolyov 6113a0039c feat: webhook payload history — store and display recent incoming payloads
Backend:
- WebhookPayloadLog model (provider_id, method, headers, body, status, extracted_fields, error_message)
- Auto-log payloads in generic_webhook() with matched/unmatched/error status
- Auto-prune beyond max_stored_payloads per provider
- Header filtering (only Content-Type, User-Agent, X-* stored; no Authorization)
- GET/DELETE /api/providers/{id}/webhook-logs endpoints
- store_payloads + max_stored_payloads in WebhookProviderConfig

Frontend:
- WebhookPayloadHistory.svelte — expandable log viewer with status badges, JSON body, headers, extracted fields
- payloadHistory flag on webhook provider descriptor
- max_stored_payloads config field (0 = disabled)
- Password confirmation field on change password modal
- i18n keys for webhook logs (en + ru)
2026-03-28 13:54:54 +03:00

77 lines
2.3 KiB
Python

"""Webhook payload log API routes."""
from __future__ import annotations
import logging
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy import delete as sa_delete
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 ServiceProvider, User, WebhookPayloadLog
from .helpers import get_owned_entity
_LOGGER = logging.getLogger(__name__)
router = APIRouter(prefix="/api/providers", tags=["webhook-logs"])
@router.get("/{provider_id}/webhook-logs")
async def list_webhook_logs(
provider_id: int,
limit: int = 20,
offset: int = 0,
session: AsyncSession = Depends(get_session),
user: User = Depends(get_current_user),
):
"""List recent webhook payload logs for a provider."""
provider = await get_owned_entity(
session, ServiceProvider, provider_id, user.id,
not_found_msg="Provider not found",
)
if provider.type != "webhook":
raise HTTPException(status_code=400, detail="Not a webhook provider")
result = await session.exec(
select(WebhookPayloadLog)
.where(WebhookPayloadLog.provider_id == provider_id)
.order_by(WebhookPayloadLog.created_at.desc())
.offset(offset)
.limit(min(limit, 100))
)
return [
{
"id": log.id,
"provider_id": log.provider_id,
"method": log.method,
"headers": log.headers,
"body": log.body,
"status": log.status,
"extracted_fields": log.extracted_fields,
"error_message": log.error_message,
"created_at": log.created_at.isoformat() if log.created_at else None,
}
for log in result.all()
]
@router.delete("/{provider_id}/webhook-logs", status_code=204)
async def clear_webhook_logs(
provider_id: int,
session: AsyncSession = Depends(get_session),
user: User = Depends(get_current_user),
):
"""Clear all webhook payload logs for a provider."""
await get_owned_entity(
session, ServiceProvider, provider_id, user.id,
not_found_msg="Provider not found",
)
await session.execute(
sa_delete(WebhookPayloadLog)
.where(WebhookPayloadLog.provider_id == provider_id)
)
await session.commit()