Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
fix(qa): pass technique_id and test_id context between test suites fix(qa): playbook creation requires technique_id field fix(qa): lesson creation requires what_happened and root_cause fields fix(qa): campaign complete test now activates with test before completing fix(qa): rate limit test notes loopback exemption instead of failing
150 lines
4.9 KiB
Python
150 lines
4.9 KiB
Python
"""Webhook configuration CRUD router — admin only.
|
|
|
|
Endpoints
|
|
---------
|
|
GET /webhooks — list all webhook configs
|
|
POST /webhooks — create a new webhook config
|
|
GET /webhooks/{id} — get a single webhook config
|
|
PATCH /webhooks/{id} — update a webhook config
|
|
DELETE /webhooks/{id} — hard-delete a webhook config
|
|
POST /webhooks/{id}/test — send a test ping
|
|
"""
|
|
|
|
import uuid
|
|
|
|
from fastapi import APIRouter, Depends, status
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.database import get_db
|
|
from app.dependencies.auth import require_any_role
|
|
from app.domain.unit_of_work import UnitOfWork
|
|
from app.models.user import User
|
|
from app.schemas.webhook import WebhookConfigCreate, WebhookConfigOut, WebhookConfigUpdate
|
|
from app.services.webhook_service import (
|
|
create_webhook,
|
|
delete_webhook,
|
|
dispatch_webhook,
|
|
get_webhook_or_raise,
|
|
list_webhooks,
|
|
update_webhook,
|
|
)
|
|
|
|
router = APIRouter(prefix="/webhooks", tags=["webhooks"])
|
|
|
|
|
|
def _mask_secret(wh) -> WebhookConfigOut:
|
|
"""Return a WebhookConfigOut with the secret masked."""
|
|
out = WebhookConfigOut.model_validate(wh)
|
|
if wh.secret:
|
|
out.secret = "***"
|
|
else:
|
|
out.secret = None
|
|
return out
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# GET /webhooks
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@router.get("", response_model=list[WebhookConfigOut])
|
|
def list_webhooks_route(
|
|
offset: int = 0,
|
|
limit: int = 50,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_any_role("admin")),
|
|
):
|
|
"""Return all webhook configurations. **Requires admin role.**"""
|
|
webhooks = list_webhooks(db, offset=offset, limit=limit)
|
|
return [_mask_secret(wh) for wh in webhooks]
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# POST /webhooks
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@router.post("", response_model=WebhookConfigOut, status_code=status.HTTP_201_CREATED)
|
|
def create_webhook_route(
|
|
payload: WebhookConfigCreate,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_any_role("admin")),
|
|
):
|
|
"""Create a new webhook configuration. **Requires admin role.**"""
|
|
with UnitOfWork(db) as uow:
|
|
wh = create_webhook(db, created_by=current_user.id, payload=payload)
|
|
uow.commit()
|
|
db.refresh(wh)
|
|
return _mask_secret(wh)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# GET /webhooks/{id}
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@router.get("/{webhook_id}", response_model=WebhookConfigOut)
|
|
def get_webhook_route(
|
|
webhook_id: uuid.UUID,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_any_role("admin")),
|
|
):
|
|
"""Return a single webhook configuration. **Requires admin role.**"""
|
|
wh = get_webhook_or_raise(db, webhook_id)
|
|
return _mask_secret(wh)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# PATCH /webhooks/{id}
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@router.patch("/{webhook_id}", response_model=WebhookConfigOut)
|
|
def update_webhook_route(
|
|
webhook_id: uuid.UUID,
|
|
payload: WebhookConfigUpdate,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_any_role("admin")),
|
|
):
|
|
"""Update one or more fields of a webhook configuration. **Requires admin role.**"""
|
|
with UnitOfWork(db) as uow:
|
|
wh = update_webhook(db, webhook_id, payload)
|
|
uow.commit()
|
|
db.refresh(wh)
|
|
return _mask_secret(wh)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# DELETE /webhooks/{id}
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@router.delete("/{webhook_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
def delete_webhook_route(
|
|
webhook_id: uuid.UUID,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_any_role("admin")),
|
|
):
|
|
"""Hard-delete a webhook configuration. **Requires admin role.**"""
|
|
with UnitOfWork(db) as uow:
|
|
delete_webhook(db, webhook_id)
|
|
uow.commit()
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# POST /webhooks/{id}/test
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
@router.post("/{webhook_id}/test", status_code=status.HTTP_202_ACCEPTED)
|
|
def test_webhook_route(
|
|
webhook_id: uuid.UUID,
|
|
db: Session = Depends(get_db),
|
|
current_user: User = Depends(require_any_role("admin")),
|
|
):
|
|
"""Send a test ping to the webhook endpoint. **Requires admin role.**"""
|
|
# Verify the webhook exists before dispatching
|
|
get_webhook_or_raise(db, webhook_id)
|
|
dispatch_webhook("webhook.test", {"webhook_id": str(webhook_id), "message": "Test ping from Aegis"})
|
|
return {"detail": "Test ping dispatched"}
|