Some checks failed
Aegis CI / lint-and-test (push) Has been cancelled
- SystemConfig model + migration b033 for runtime key-value config - GET/PATCH /system/email-config + POST /system/email-test (admin only) - email_service reads SMTP config from DB (overrides .env) - Webhooks now accessible to red_lead/blue_lead + admin - GET /users/me already existed; /users/me/preferences already working - SettingsPage with 4 role-aware tabs: * Profile & Jira: jira_account_id, user info * Notifications: role-specific email/in-app toggles (12 prefs) * Webhooks: full CRUD + test ping (leads + admin) * Email/SMTP: enable toggle, server config, test email (admin only) - Added /settings route (all authenticated users) - Settings link added to Sidebar Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
150 lines
5.0 KiB
Python
150 lines
5.0 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("red_lead", "blue_lead")),
|
|
):
|
|
"""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("red_lead", "blue_lead")),
|
|
):
|
|
"""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("red_lead", "blue_lead")),
|
|
):
|
|
"""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("red_lead", "blue_lead")),
|
|
):
|
|
"""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("red_lead", "blue_lead")),
|
|
):
|
|
"""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("red_lead", "blue_lead")),
|
|
):
|
|
"""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"}
|