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