Files
app-service/backend/app/api/v1/webhooks.py
matteoscrugli 8c4a555b88 Add comprehensive backend features and mobile UI improvements
Backend:
- Add 2FA authentication with TOTP support
- Add API keys management system
- Add audit logging for security events
- Add file upload/management system
- Add notifications system with preferences
- Add session management
- Add webhooks integration
- Add analytics endpoints
- Add export functionality
- Add password policy enforcement
- Add new database migrations for core tables

Frontend:
- Add module position system (top/bottom sidebar sections)
- Add search and notifications module configuration tabs
- Add mobile logo replacing hamburger menu
- Center page title absolutely when no tabs present
- Align sidebar footer toggles with navigation items
- Add lighter icon color in dark theme for mobile
- Add API keys management page
- Add notifications page with context
- Add admin analytics and audit logs pages

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-17 22:27:32 +01:00

381 lines
11 KiB
Python

"""Webhook management endpoints."""
import json
from typing import Any, List
from fastapi import APIRouter, Depends, HTTPException, status, BackgroundTasks
from sqlalchemy.orm import Session
from app.dependencies import get_db, get_current_superuser
from app.models.user import User
from app import crud
from app.schemas.webhook import (
WebhookCreate,
WebhookUpdate,
Webhook as WebhookSchema,
WebhookWithSecret,
WebhookDelivery as WebhookDeliverySchema,
WebhookTest,
WEBHOOK_EVENTS,
)
router = APIRouter()
@router.get("/", response_model=List[WebhookSchema])
def list_webhooks(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_superuser),
skip: int = 0,
limit: int = 100,
is_active: bool = None
):
"""
List all webhooks.
Requires superuser permissions.
"""
webhooks = crud.webhook.get_multi(db, skip=skip, limit=limit, is_active=is_active)
# Convert events from JSON string to list
result = []
for webhook in webhooks:
webhook_dict = {
"id": webhook.id,
"name": webhook.name,
"url": webhook.url,
"secret": webhook.secret,
"events": json.loads(webhook.events) if webhook.events else [],
"is_active": webhook.is_active,
"retry_count": webhook.retry_count,
"timeout_seconds": webhook.timeout_seconds,
"created_by": webhook.created_by,
"created_at": webhook.created_at,
"updated_at": webhook.updated_at,
"last_triggered_at": webhook.last_triggered_at,
"success_count": webhook.success_count,
"failure_count": webhook.failure_count,
}
result.append(webhook_dict)
return result
@router.post("/", response_model=WebhookWithSecret, status_code=status.HTTP_201_CREATED)
def create_webhook(
webhook_in: WebhookCreate,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_superuser),
):
"""
Create a new webhook.
Returns the webhook with its secret (only shown once at creation).
Requires superuser permissions.
"""
webhook = crud.webhook.create(db, obj_in=webhook_in, created_by=current_user.id)
# Log the action
crud.audit_log.log_action(
db,
user_id=current_user.id,
username=current_user.username,
action="create",
resource_type="webhook",
resource_id=webhook.id,
details={"name": webhook.name, "url": webhook.url},
status="success"
)
return {
"id": webhook.id,
"name": webhook.name,
"url": webhook.url,
"secret": webhook.secret,
"events": json.loads(webhook.events) if webhook.events else [],
"is_active": webhook.is_active,
"retry_count": webhook.retry_count,
"timeout_seconds": webhook.timeout_seconds,
"created_by": webhook.created_by,
"created_at": webhook.created_at,
"updated_at": webhook.updated_at,
"last_triggered_at": webhook.last_triggered_at,
"success_count": webhook.success_count,
"failure_count": webhook.failure_count,
}
@router.get("/events", response_model=List[str])
def list_webhook_events(
current_user: User = Depends(get_current_superuser),
):
"""
List all available webhook event types.
"""
return WEBHOOK_EVENTS
@router.get("/{webhook_id}", response_model=WebhookSchema)
def get_webhook(
webhook_id: str,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_superuser),
):
"""
Get a specific webhook.
Requires superuser permissions.
"""
webhook = crud.webhook.get(db, id=webhook_id)
if not webhook:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Webhook not found"
)
return {
"id": webhook.id,
"name": webhook.name,
"url": webhook.url,
"secret": webhook.secret,
"events": json.loads(webhook.events) if webhook.events else [],
"is_active": webhook.is_active,
"retry_count": webhook.retry_count,
"timeout_seconds": webhook.timeout_seconds,
"created_by": webhook.created_by,
"created_at": webhook.created_at,
"updated_at": webhook.updated_at,
"last_triggered_at": webhook.last_triggered_at,
"success_count": webhook.success_count,
"failure_count": webhook.failure_count,
}
@router.put("/{webhook_id}", response_model=WebhookSchema)
def update_webhook(
webhook_id: str,
webhook_in: WebhookUpdate,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_superuser),
):
"""
Update a webhook.
Requires superuser permissions.
"""
webhook = crud.webhook.get(db, id=webhook_id)
if not webhook:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Webhook not found"
)
webhook = crud.webhook.update(db, db_obj=webhook, obj_in=webhook_in)
# Log the action
crud.audit_log.log_action(
db,
user_id=current_user.id,
username=current_user.username,
action="update",
resource_type="webhook",
resource_id=webhook.id,
details={"name": webhook.name},
status="success"
)
return {
"id": webhook.id,
"name": webhook.name,
"url": webhook.url,
"secret": webhook.secret,
"events": json.loads(webhook.events) if webhook.events else [],
"is_active": webhook.is_active,
"retry_count": webhook.retry_count,
"timeout_seconds": webhook.timeout_seconds,
"created_by": webhook.created_by,
"created_at": webhook.created_at,
"updated_at": webhook.updated_at,
"last_triggered_at": webhook.last_triggered_at,
"success_count": webhook.success_count,
"failure_count": webhook.failure_count,
}
@router.delete("/{webhook_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_webhook(
webhook_id: str,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_superuser),
):
"""
Delete a webhook.
Requires superuser permissions.
"""
webhook = crud.webhook.get(db, id=webhook_id)
if not webhook:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Webhook not found"
)
# Log the action
crud.audit_log.log_action(
db,
user_id=current_user.id,
username=current_user.username,
action="delete",
resource_type="webhook",
resource_id=webhook_id,
details={"name": webhook.name},
status="success"
)
crud.webhook.delete(db, id=webhook_id)
return None
@router.post("/{webhook_id}/regenerate-secret", response_model=WebhookWithSecret)
def regenerate_webhook_secret(
webhook_id: str,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_superuser),
):
"""
Regenerate the secret for a webhook.
Returns the new secret (only shown once).
Requires superuser permissions.
"""
webhook = crud.webhook.get(db, id=webhook_id)
if not webhook:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Webhook not found"
)
webhook = crud.webhook.regenerate_secret(db, db_obj=webhook)
# Log the action
crud.audit_log.log_action(
db,
user_id=current_user.id,
username=current_user.username,
action="regenerate_secret",
resource_type="webhook",
resource_id=webhook.id,
details={"name": webhook.name},
status="success"
)
return {
"id": webhook.id,
"name": webhook.name,
"url": webhook.url,
"secret": webhook.secret,
"events": json.loads(webhook.events) if webhook.events else [],
"is_active": webhook.is_active,
"retry_count": webhook.retry_count,
"timeout_seconds": webhook.timeout_seconds,
"created_by": webhook.created_by,
"created_at": webhook.created_at,
"updated_at": webhook.updated_at,
"last_triggered_at": webhook.last_triggered_at,
"success_count": webhook.success_count,
"failure_count": webhook.failure_count,
}
@router.post("/{webhook_id}/test", response_model=WebhookDeliverySchema)
async def test_webhook(
webhook_id: str,
test_data: WebhookTest = None,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_superuser),
):
"""
Send a test delivery to a webhook.
Requires superuser permissions.
"""
webhook = crud.webhook.get(db, id=webhook_id)
if not webhook:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Webhook not found"
)
event_type = test_data.event_type if test_data else "test.ping"
payload = test_data.payload if test_data and test_data.payload else None
delivery = await crud.webhook_service.test_webhook(
db,
webhook=webhook,
event_type=event_type,
payload=payload
)
# Log the action
crud.audit_log.log_action(
db,
user_id=current_user.id,
username=current_user.username,
action="test",
resource_type="webhook",
resource_id=webhook.id,
details={"status": delivery.status},
status="success"
)
return delivery
@router.get("/{webhook_id}/deliveries", response_model=List[WebhookDeliverySchema])
def list_webhook_deliveries(
webhook_id: str,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_superuser),
skip: int = 0,
limit: int = 50
):
"""
List deliveries for a specific webhook.
Requires superuser permissions.
"""
webhook = crud.webhook.get(db, id=webhook_id)
if not webhook:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Webhook not found"
)
return crud.webhook_delivery.get_by_webhook(
db,
webhook_id=webhook_id,
skip=skip,
limit=limit
)
@router.post("/{webhook_id}/deliveries/{delivery_id}/retry", response_model=WebhookDeliverySchema)
async def retry_webhook_delivery(
webhook_id: str,
delivery_id: str,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_superuser),
):
"""
Retry a failed webhook delivery.
Requires superuser permissions.
"""
webhook = crud.webhook.get(db, id=webhook_id)
if not webhook:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Webhook not found"
)
delivery = crud.webhook_delivery.get(db, id=delivery_id)
if not delivery or delivery.webhook_id != webhook_id:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Delivery not found"
)
await crud.webhook_service.deliver(db, webhook, delivery)
db.refresh(delivery)
return delivery