lib/plausible.ex

defmodule Plausible do
  @moduledoc """
  `Plausible` is a library to push analytics event to [Plausible Analytics]("https://plausible.io").
  The library provides two working mode to push events.

  ## Installation

  1. Add `:plausible` to your project `mix.exs` dependencies

  ```elixir
    def deps() do
      [ #other dependendencies excluded here
        {:plausible, "~> 0.1.0"}
      ]
    end
  ```

  2. Initialize `finch` process in your app supervision tree.

  ```elixir
    def start(_type, _arg) do
      children = [{Finch, name: MyApp.Finch}]
    end
  ```

  ## Usages

  The library provides two working modes as listed below

  1. Use `Plausible.Client` as the wrapper to `Plausible` module so you can pass in less parameters.
  2. Or use `Plausible` directly that accepts a finch process name and keyword `opts` parameter directly to send event to Plausible Analytics Host. 

  The document can be found on each `Plausible` and `Plausible.Client`

  ## Contributions

  All contributions are welcome. Please visit https://github.com/blisscs/plausible and create issue or pull request there.
  """

  @doc """
  Create Plausible Event

  Currently only returns `:ok`

  ## Examples

      iex> create_event(MyApp.Finch, user_agent: user_agent, x_forwarded_for: x_forwarded_for, url: url, domain: domain)
      :ok
  """
  @spec create_event(String.t(), atom(), Keyword.t() | map) ::
          :ok | {:error, Exception.t()} | {:error, Mint.Types.status(), binary()}
  def create_event(endpoint \\ "https://plausible.io", finch_process, opts) do
    path = "/api/event"

    user_agent =
      opts[:user_agent] ||
        raise """
        user_agent not defined
        """

    x_forwarded_for =
      opts[:x_forwarded_for] ||
        raise """
        x_forwarded_for not defined
        """

    domain =
      opts[:domain] ||
        raise """
        domain not defined
        """

    url =
      opts[:url] ||
        raise """
        url not defined
        """

    referrer = opts[:referrer]

    event_name = opts[:event_name] || "pageview"

    body = %{name: event_name, url: url, referrer: referrer, domain: domain} |> Jason.encode!()

    # TODO handle custom properties

    Finch.build(
      :post,
      "#{endpoint}#{path}",
      [
        {"User-Agent", user_agent},
        {"X-Forwarded-For", x_forwarded_for},
        {"Content-Type", "application/json"}
      ],
      body
    )
    |> Finch.request(finch_process)
    |> handle_request()
  end

  @spec create_event!(String.t(), atom(), Keyword.t() | map()) :: :ok
  def create_event!(endpoint \\ "https://plausible.io", finch_process, opts) do
    create_event(endpoint, finch_process, opts)
    |> case do
      :ok ->
        :ok

      {:error, code, message} ->
        raise Plausible.Exception, message: "status_code: #{code}, error: #{message}"

      {:error, %Mint.TransportError{} = mint_error} ->
        raise Plausible.Exception, message: to_string(mint_error.reason)
    end
  end

  defp handle_request({:error, _} = error), do: error

  defp handle_request({:ok, %Finch.Response{status: 202}}), do: :ok

  defp handle_request({:ok, %Finch.Response{status: status, body: body}}),
    do: {:error, status, body}
end