Packages

A Fair Source multi-agent runtime with deterministic agent scoring and replayable run history.

Current section

Files

Jump to
syntropy lib syntropy_web controllers api webhook_controller.ex
Raw

lib/syntropy_web/controllers/api/webhook_controller.ex

defmodule SyntropyWeb.Api.WebhookController do
@moduledoc """
REST management of webhook endpoint registrations and delivery logs.
"""
use SyntropyWeb, :controller
import SyntropyWeb.Api.ControllerHelpers
alias Syntropy.Webhooks.Registry, as: WebhookRegistry
@doc "GET /api/webhooks — list registrations with recent delivery attempts."
def index(conn, _params) do
data =
WebhookRegistry.list()
|> Enum.map(fn {webhook, deliveries} -> webhook_json(webhook, deliveries) end)
json(conn, %{data: data})
end
@doc "POST /api/webhooks — register an endpoint. Returns the secret once."
def create(conn, params) do
case WebhookRegistry.register(params) do
{:ok, webhook} ->
conn
|> put_status(:created)
|> json(%{data: Map.put(webhook_json(webhook, []), :secret, webhook.secret)})
{:error, errors} ->
invalid_request(conn, "Webhook registration is invalid.", errors)
end
end
@doc "PATCH /api/webhooks/:id — pause or resume deliveries."
def update(conn, %{"id" => id} = params) do
case Map.get(params, "active") do
active when is_boolean(active) ->
case WebhookRegistry.set_active(id, active) do
{:ok, webhook} -> json(conn, %{data: webhook_json(webhook, [])})
{:error, :not_found} -> not_found(conn, "Webhook", id)
end
_other ->
invalid_request(conn, "Webhook update is invalid.", [
%{field: "active", message: "must be a boolean"}
])
end
end
@doc "DELETE /api/webhooks/:id — remove a registration."
def delete(conn, %{"id" => id}) do
case WebhookRegistry.remove(id) do
:ok -> json(conn, %{data: %{id: id, removed: true}})
{:error, :not_found} -> not_found(conn, "Webhook", id)
end
end
defp webhook_json(webhook, deliveries) do
%{
id: webhook.id,
url: webhook.url,
events: webhook.events,
active: webhook.active,
masked_secret: mask_secret(webhook.secret),
inserted_at: DateTime.to_iso8601(webhook.inserted_at),
recent_deliveries: Enum.map(deliveries, &delivery_json/1)
}
end
defp delivery_json(delivery) do
%{
event_id: delivery.event_id,
status: delivery.status,
http_status: delivery.http_status,
attempts: delivery.attempts,
error: delivery.error,
delivered_at: DateTime.to_iso8601(delivery.delivered_at)
}
end
defp mask_secret(secret) do
"••••" <> String.slice(secret, -4, 4)
end
end