Skip to main content

lib/meili/key.ex

defmodule Meili.Key do
  @moduledoc """
  Manages Meilisearch API keys.
  """

  alias Meili.Client

  @doc """
  Lists all API keys.

  ## Options
  - `limit`: The maximum number of keys to return.
  - `offset`: The number of keys to skip.

  ## Examples
      Meili.Key.list()
      Meili.Key.list(limit: 5)
      Meili.Key.list(client, limit: 5)
  """
  @spec list(Meili.Client.t() | Keyword.t() | nil, Keyword.t()) ::
          {:ok, map()} | {:error, Meili.Error.t()}
  def list(client_or_opts \\ nil, opts \\ []) do
    case client_or_opts do
      %Client{} = client ->
        do_list(client, opts)

      nil ->
        do_list(Meili.default_client(), [])

      opts when is_list(opts) ->
        do_list(Meili.default_client(), opts)
    end
  end

  @doc """
  Lists all API keys, raising on error.

  ## Examples
      Meili.Key.list!()
      Meili.Key.list!(limit: 5)
      Meili.Key.list!(client, limit: 5)
  """
  @spec list!(Meili.Client.t() | Keyword.t() | nil, Keyword.t()) :: map() | no_return()
  def list!(client_or_opts \\ nil, opts \\ []) do
    case list(client_or_opts, opts) do
      {:ok, body} -> body
      {:error, exception} -> raise exception
    end
  end

  defp do_list(client, opts) do
    params = Keyword.take(opts, [:limit, :offset])
    Client.request(client, :get, "/keys", params: params)
  end

  @doc """
  Retrieves a single API key by its value or UUID.

  ## Examples
      Meili.Key.get("some-key-value-or-uuid")
      Meili.Key.get(client, "some-key-value-or-uuid")
  """
  @spec get(Meili.Client.t() | String.t(), String.t() | nil) ::
          {:ok, map()} | {:error, Meili.Error.t()}
  def get(client_or_key, key_or_nil \\ nil) do
    case client_or_key do
      %Client{} = client ->
        Client.request(client, :get, "/keys/#{key_or_nil}")

      key when is_binary(key) ->
        Client.request(Meili.default_client(), :get, "/keys/#{key}")
    end
  end

  @doc """
  Retrieves a single API key, raising on error.

  ## Examples
      Meili.Key.get!("some-key-value-or-uuid")
      Meili.Key.get!(client, "some-key-value-or-uuid")
  """
  @spec get!(Meili.Client.t() | String.t(), String.t() | nil) :: map() | no_return()
  def get!(client_or_key, key_or_nil \\ nil) do
    case get(client_or_key, key_or_nil) do
      {:ok, body} -> body
      {:error, exception} -> raise exception
    end
  end

  @doc """
  Creates a new API key.

  Keys are automatically camelized from snake_case to camelCase.

  ## Options
  - `name`: Human-readable name.
  - `description`: Purpose of the key.
  - `actions`: List of permitted actions (e.g. `["search"]`).
  - `indexes`: List of accessible indexes.
  - `expires_at` / `expiresAt`: Expiration date (RFC 3339 string).

  ## Examples
      params = %{
        name: "Search Key",
        actions: ["search"],
        indexes: ["*"],
        expires_at: nil
      }
      Meili.Key.create(params)
      Meili.Key.create(client, params)
  """
  @spec create(Meili.Client.t() | map() | Keyword.t(), map() | Keyword.t() | nil) ::
          {:ok, map()} | {:error, Meili.Error.t()}
  def create(client_or_params, params_or_nil \\ nil) do
    case client_or_params do
      %Client{} = client ->
        do_create(client, params_or_nil)

      params when is_map(params) or is_list(params) ->
        do_create(Meili.default_client(), params)
    end
  end

  @doc """
  Creates a new API key, raising on error.

  ## Examples
      params = %{
        name: "Search Key",
        actions: ["search"],
        indexes: ["*"]
      }
      Meili.Key.create!(params)
      Meili.Key.create!(client, params)
  """
  @spec create!(Meili.Client.t() | map() | Keyword.t(), map() | Keyword.t() | nil) ::
          map() | no_return()
  def create!(client_or_params, params_or_nil \\ nil) do
    case create(client_or_params, params_or_nil) do
      {:ok, body} -> body
      {:error, exception} -> raise exception
    end
  end

  defp do_create(client, params) do
    body = Meili.Util.camelize_keys(params)
    Client.request(client, :post, "/keys", json: body)
  end

  @doc """
  Updates an existing API key. Only the name and description can be updated.

  ## Examples
      Meili.Key.update("key_value", name: "New Name", description: "New Description")
      Meili.Key.update(client, "key_value", %{name: "New Name"})
  """
  @spec update(
          Meili.Client.t() | String.t(),
          String.t() | map() | Keyword.t() | nil,
          map() | Keyword.t() | nil
        ) :: {:ok, map()} | {:error, Meili.Error.t()}
  def update(client_or_key, key_or_params \\ nil, params_or_nil \\ nil) do
    case client_or_key do
      %Client{} = client ->
        do_update(client, key_or_params, params_or_nil)

      key when is_binary(key) ->
        do_update(Meili.default_client(), key, key_or_params)
    end
  end

  @doc """
  Updates an API key, raising on error.

  ## Examples
      Meili.Key.update!("key_value", name: "New Name")
      Meili.Key.update!(client, "key_value", %{name: "New Name"})
  """
  @spec update!(
          Meili.Client.t() | String.t(),
          String.t() | map() | Keyword.t() | nil,
          map() | Keyword.t() | nil
        ) :: map() | no_return()
  def update!(client_or_key, key_or_params \\ nil, params_or_nil \\ nil) do
    case update(client_or_key, key_or_params, params_or_nil) do
      {:ok, body} -> body
      {:error, exception} -> raise exception
    end
  end

  defp do_update(client, key, params) do
    body = Meili.Util.camelize_keys(params)
    Client.request(client, :patch, "/keys/#{key}", json: body)
  end

  @doc """
  Deletes an API key.

  ## Examples
      Meili.Key.delete("key_value")
      Meili.Key.delete(client, "key_value")
  """
  @spec delete(Meili.Client.t() | String.t(), String.t() | nil) ::
          {:ok, term()} | {:error, Meili.Error.t()}
  def delete(client_or_key, key_or_nil \\ nil) do
    case client_or_key do
      %Client{} = client ->
        Client.request(client, :delete, "/keys/#{key_or_nil}")

      key when is_binary(key) ->
        Client.request(Meili.default_client(), :delete, "/keys/#{key}")
    end
  end

  @doc """
  Deletes an API key, raising on error.

  ## Examples
      Meili.Key.delete!("key_value")
      Meili.Key.delete!(client, "key_value")
  """
  @spec delete!(Meili.Client.t() | String.t(), String.t() | nil) :: term() | no_return()
  def delete!(client_or_key, key_or_nil \\ nil) do
    case delete(client_or_key, key_or_nil) do
      {:ok, body} -> body
      {:error, exception} -> raise exception
    end
  end
end