lib/ex_open_sea/assets/index.ex

defmodule ExOpenSea.Assets.Index do
  @moduledoc """
  Return a cursor to a list of NFTs based on the specified filter parameters.

  Auctions created on OpenSea don't use an escrow contract, which enables gas-free auctions and
  allows users to retain ownership of their items while they're on sale. In these cases, some
  NFTs from opensea.io may not appear in the API until a transaction has been completed.

  https://docs.opensea.io/reference/getting-assets
  """

  alias ExOpenSea.Http

  @type api_key :: ExOpenSea.ApiKey.t()
  @type params :: %{
          optional(:owner) => String.t(),
          optional(:token_ids) => [String.t()],
          # unsure how :collection is different to :collection_slug based on the docs???
          optional(:collection) => String.t(),
          optional(:collection_slug) => String.t(),
          optional(:order_direction) => String.t(),
          optional(:asset_contract_address) => String.t(),
          optional(:asset_contract_addresses) => [String.t()],
          optional(:limit) => String.t(),
          optional(:cursor) => String.t(),
          optional(:include_orders) => boolean
        }
  @type assets_cursor :: ExOpenSea.AssetsCursor.t()
  @type error_reason :: :parse_result_item | String.t()
  @type raw_payload :: map
  @type result :: {:ok, assets_cursor} | {:error, error_reason, raw_payload | nil}

  @query_encoder ExOpenSea.QueryEncoders.SingleKeyMultipleValueWwwForm

  @spec get(api_key) :: result
  @spec get(api_key, params) :: result
  def get(api_key, params \\ %{}) do
    "/api/v1/assets"
    |> Http.Request.for_path()
    |> Http.Request.with_query(params, @query_encoder)
    |> Http.Request.with_auth(api_key)
    |> Http.Client.get()
    |> parse_response()
  end

  defp parse_response({
         :ok,
         %{"assets" => assets, "next" => next, "previous" => previous} = raw_payload
       }) do
    assets
    |> Enum.map(&Mapail.map_to_struct(&1, ExOpenSea.Asset))
    |> Enum.reduce(
      {:ok, []},
      fn
        {:ok, a}, {:ok, acc} -> {:ok, acc ++ [a]}
        _, _acc -> {:error, :parse_result_item}
      end
    )
    |> case do
      {:ok, struct_assets} ->
        asset_cursor = %ExOpenSea.AssetsCursor{
          assets: struct_assets,
          next: next,
          previous: previous
        }

        {:ok, asset_cursor, raw_payload}

      {:error, reason} ->
        {:error, reason, raw_payload}
    end
  end

  defp parse_response({:error, response_reasons}) when is_map(response_reasons) do
    reasons =
      response_reasons
      |> Enum.reduce(
        [],
        fn {k, v}, acc ->
          acc ++ [{k, v}]
        end
      )

    {:error, reasons, nil}
  end

  defp parse_response({:error, reason}) do
    {:error, reason, nil}
  end
end