Skip to main content

lib/codat/platform/webhooks.ex

defmodule Codat.Platform.Webhooks do
  @moduledoc """
  Manage webhook consumers via the Codat Platform API.

  Webhook consumers are HTTPS endpoints that Codat POSTs to when events occur.
  This module wraps the Platform API for creating and managing them programmatically.

  For receiving and verifying webhooks in your Elixir app, see:
  - `Codat.Webhooks.Plug` — drop-in Plug for Phoenix/Plug apps
  - `Codat.Webhooks.Verifier` — standalone signature verification
  - `Codat.Webhooks.Handler` — behaviour for typed event handling

  ## Example

      # Create a webhook consumer
      {:ok, consumer} = Codat.Platform.Webhooks.create(client, %{
        name: "Production Events",
        url: "https://myapp.com/webhooks/codat",
        eventTypes: ["company.dataConnectionStatusChanged", "invoices.write.successful"]
      })

      # List all configured consumers
      {:ok, page} = Codat.Platform.Webhooks.list(client)

  """

  alias Codat.Client
  alias Codat.Error
  alias Codat.Pagination

  alias Codat.Platform.Shared
  import Codat.API, only: [resolve_client: 1]

  @base_path "/webhooks"

  # ---------------------------------------------------------------------------
  # List
  # ---------------------------------------------------------------------------

  @doc """
  Lists all webhook consumers.

  ## Example

      {:ok, page} = Codat.Platform.Webhooks.list(client)
      consumers = page.results

  """
  @spec list(Client.t() | keyword(), keyword()) ::
          {:ok, Pagination.t()} | {:error, Error.t()}
  def list(client_or_opts \\ [], opts \\ [])

  def list(%Client{} = client, opts) do
    Shared.paginated_get(client.config, @base_path, opts, __MODULE__, :list)
  end

  def list(opts, []) when is_list(opts) do
    list(resolve_client(opts), opts)
  end

  # ---------------------------------------------------------------------------
  # Get
  # ---------------------------------------------------------------------------

  @doc """
  Fetches a webhook consumer by ID.

  ## Example

      {:ok, consumer} = Codat.Platform.Webhooks.get(client, "webhook-id")
      consumer["url"]         # => "https://myapp.com/webhooks/codat"
      consumer["status"]      # => "Active"

  """
  @spec get(Client.t() | String.t(), String.t() | keyword()) ::
          {:ok, map()} | {:error, Error.t()}
  def get(client_or_id, id_or_opts \\ [])

  def get(%Client{} = client, id) when is_binary(id) do
    Codat.HTTP.Client.get(client.config, "#{@base_path}/#{id}",
      api_module: __MODULE__,
      operation: :get
    )
  end

  def get(id, opts) when is_binary(id) do
    get(resolve_client(opts), id)
  end

  # ---------------------------------------------------------------------------
  # Create
  # ---------------------------------------------------------------------------

  @doc """
  Creates a new webhook consumer endpoint.

  ## Required Fields

  - `url` — HTTPS endpoint to POST events to (must respond with 2xx within 15s)

  ## Optional Fields

  - `name` — human-readable label
  - `eventTypes` — list of event type strings to subscribe to.
    Pass `["*"]` to receive all events. Defaults to all events if omitted.
  - `companyId` — scope to a single company
  - `type` — `"Portal"` or `"API"` (informational)

  ## Example

      {:ok, consumer} = Codat.Platform.Webhooks.create(client, %{
        name: "Invoice Events",
        url: "https://myapp.com/webhooks/codat",
        eventTypes: [
          "invoices.write.successful",
          "invoices.write.unsuccessful"
        ]
      })

  """
  @spec create(Client.t() | map(), map() | keyword()) ::
          {:ok, map()} | {:error, Error.t()}
  def create(client_or_body, body_or_opts \\ [])

  def create(%Client{} = client, body) when is_map(body) do
    Codat.HTTP.Client.post(client.config, @base_path, body,
      api_module: __MODULE__,
      operation: :create
    )
  end

  def create(body, opts) when is_map(body) do
    create(resolve_client(opts), body)
  end

  # ---------------------------------------------------------------------------
  # Update
  # ---------------------------------------------------------------------------

  @doc """
  Updates an existing webhook consumer.

  ## Example

      {:ok, consumer} = Codat.Platform.Webhooks.update(client, "webhook-id", %{
        eventTypes: ["*"]
      })

  """
  @spec update(Client.t() | String.t(), String.t() | map(), map() | keyword()) ::
          {:ok, map()} | {:error, Error.t()}
  def update(client_or_id, id_or_body, body_or_opts \\ [])

  def update(%Client{} = client, id, body) when is_binary(id) and is_map(body) do
    Codat.HTTP.Client.put(client.config, "#{@base_path}/#{id}", body,
      api_module: __MODULE__,
      operation: :update
    )
  end

  def update(id, body, opts) when is_binary(id) and is_map(body) do
    update(resolve_client(opts), id, body)
  end

  # ---------------------------------------------------------------------------
  # Delete
  # ---------------------------------------------------------------------------

  @doc """
  Deletes a webhook consumer.

  ## Example

      {:ok, nil} = Codat.Platform.Webhooks.delete(client, "webhook-id")

  """
  @spec delete(Client.t() | String.t(), String.t() | keyword()) ::
          {:ok, nil} | {:error, Error.t()}
  def delete(client_or_id, id_or_opts \\ [])

  def delete(%Client{} = client, id) when is_binary(id) do
    Codat.HTTP.Client.delete(client.config, "#{@base_path}/#{id}",
      api_module: __MODULE__,
      operation: :delete
    )
  end

  def delete(id, opts) when is_binary(id) do
    delete(resolve_client(opts), id)
  end

  # ---------------------------------------------------------------------------
  # Enable / Disable
  # ---------------------------------------------------------------------------

  @doc """
  Enables a previously disabled webhook consumer.

  ## Example

      {:ok, consumer} = Codat.Platform.Webhooks.enable(client, "webhook-id")
      consumer["status"]  # => "Active"

  """
  @spec enable(Client.t() | String.t(), String.t() | keyword()) ::
          {:ok, map()} | {:error, Error.t()}
  def enable(client_or_id, id_or_opts \\ [])

  def enable(%Client{} = client, id) when is_binary(id) do
    Codat.HTTP.Client.post(client.config, "#{@base_path}/#{id}/enable", %{},
      api_module: __MODULE__,
      operation: :enable
    )
  end

  def enable(id, opts) when is_binary(id) do
    enable(resolve_client(opts), id)
  end

  @doc """
  Disables a webhook consumer so it no longer receives events.

  ## Example

      {:ok, consumer} = Codat.Platform.Webhooks.disable(client, "webhook-id")
      consumer["status"]  # => "Inactive"

  """
  @spec disable(Client.t() | String.t(), String.t() | keyword()) ::
          {:ok, map()} | {:error, Error.t()}
  def disable(client_or_id, id_or_opts \\ [])

  def disable(%Client{} = client, id) when is_binary(id) do
    Codat.HTTP.Client.post(client.config, "#{@base_path}/#{id}/disable", %{},
      api_module: __MODULE__,
      operation: :disable
    )
  end

  def disable(id, opts) when is_binary(id) do
    disable(resolve_client(opts), id)
  end

  # ---------------------------------------------------------------------------
  # Message log
  # ---------------------------------------------------------------------------

  @doc """
  Returns the delivery log for a specific webhook consumer.

  ## Example

      {:ok, page} = Codat.Platform.Webhooks.messages(client, "webhook-id")
      msgs = page.results

  """
  @spec messages(Client.t() | String.t(), String.t() | keyword(), keyword()) ::
          {:ok, Pagination.t()} | {:error, Error.t()}
  def messages(client_or_id, id_or_opts \\ [], opts \\ [])

  def messages(%Client{} = client, id, opts) when is_binary(id) do
    params = Pagination.to_params(opts)

    case Codat.HTTP.Client.get(client.config, "#{@base_path}/#{id}/messages",
           params: params,
           api_module: __MODULE__,
           operation: :messages
         ) do
      {:ok, body} when is_map(body) -> {:ok, Pagination.from_response(body)}
      {:error, _reason} = error -> error
    end
  end

  def messages(id, opts, []) when is_binary(id) do
    messages(resolve_client(opts), id, opts)
  end

  def messages(id, [], []) when is_binary(id) do
    messages(resolve_client([]), id, [])
  end
end