lib/api/v2/resources/user.ex

defmodule Patreon.API.V2.Resource.User do
  require Logger

  @typedoc """
  Represents a Patreon user.

  * `:about` - The user's about text, which appears on their profile.
  * `:can_see_nsfw` - `true` if this user can view nsfw content.
  * `:created` - Datetime of this user's account creation.
  * `:email` - The user's email address. Requires certain scopes to access. See the scopes section of the patreon API documentaiton.
  * `:first_name` - The users first name.
  * `:full_name` - The users full name.
  * `:hide_pledges` - `true` if the user has chosen to keep private which creators they pledge to.
  * `:image_url` - The user's profile picture URL, scaled to width 400px.
  * `:is_email_verified` - `true` if the user has confirmed their email.
  * `:last_name` - The users last name.
  * `:like_count` - How many posts the user has liked.
  * `:social_connections` - Mapping from user's connected app names to external user id on the respective app.
  * `:thumb_url` - The user's profile picture URL, scaled to a square of size 100x100px.
  * `:url` - URL of this user's creator or patron profile.
  * `:vanity` - The public "username" of the user.
  """
  @type t :: %__MODULE__{
    id: String.t,
    about:   String.t | nil,
    can_see_nsfw: boolean | nil,
    created: DateTime.t | nil,
    email: String.t | nil,
    first_name: String.t | nil,
    full_name: String.t | nil,
    hide_pledges: boolean | nil,
    image_url: String.t | nil,
    is_email_verified: boolean | nil,
    last_name: String.t | nil,
    like_count: integer | nil,
    social_connections: Patreon.API.V2.Resource.SocialConnections.t | nil,
    thumb_url: String.t | nil,
    url: String.t | nil,
    vanity: String.t | nil,
    included: %{campaign: Patreon.API.V2.Resource.Campaign.t | nil, memberships: any | []}
  }

  defstruct [
    :id,
    :about,
    :can_see_nsfw,
    :created,
    :email,
    :first_name,
    :full_name,
    :hide_pledges,
    :image_url,
    :is_email_verified,
    :last_name,
    :like_count,
    :social_connections,
    :thumb_url,
    :url,
    :vanity,
    :included,
  ]

  @spec from_response(%{data: map, included: list(map)}) :: %__MODULE__{}
  def from_response(%{data: data, included: includes}) do
    user =
      %{id: data.id}
      |> Map.merge(data.attributes)
      |> Map.put_new(:included, %{campaign: nil, memberships: []})
      |> add_includes(includes)

      Kernel.struct(__MODULE__, user)
  end

  @spec from_response(%{data: map}) :: %__MODULE__{}
  def from_response(%{data: data}) do
    from_response(%{data: data, included: []})
  end

  def add_includes(user, includes) do
    %{user | included: Enum.reduce(includes, user.included, &struct_from_include/2)}
  end

  def struct_from_include(%{type: "campaign"} = include, %{campaign: nil, memberships: _memberships} = user_includes) do
    %{user_includes | campaign: Patreon.API.V2.Resource.Campaign.from_response(include)}
  end

  def struct_from_include(%{type: "campaign"} = _include, %{campaign: _existing_campaign, memberships: _memberships} = user_includes) do
    Logger.warn "User already contained an existing campaign."
    user_includes
  end

  def struct_from_include(%{type: "member"} = include, %{campaign: _campaign, memberships: memberships} = user_includes) do
    %{user_includes | memberships: [Patreon.API.V2.Resource.Member.from_response(include) | memberships]}
  end

  def opts_to_query([]) do
    []
  end

  def opts_to_query(include_fields) do
    Enum.reduce(include_fields, ["fields[user]": "", include: "", "fields[campaign]": "", "fields[member]": ""], &generate_query_option/2)
    |> Keyword.filter(fn({_key, val}) -> val != "" end)
  end

  defp generate_query_option({:user, []}, acc) do
    acc
  end

  defp generate_query_option({:user, user_fields}, acc) do
    Keyword.put(acc, :"fields[user]", Enum.join(user_fields, ","))
  end

  defp generate_query_option({:campaign, campaign_fields}, acc) do
    updated_include =
    [Keyword.get(acc, :include), "campaign"]
    |> Enum.filter(fn(val) -> val != "" end)
    |> Enum.join(",")

    Keyword.put(acc, :"fields[campaign]", Enum.join(campaign_fields, ","))
    |> Keyword.put(:include, updated_include)
  end

  defp generate_query_option({:memberships, memberships_fields}, acc) do
    updated_include =
    [Keyword.get(acc, :include), "memberships"]
    |> Enum.filter(fn(val) -> val != "" end)
    |> Enum.join(",")

    Keyword.put(acc, :"fields[memberships]", Enum.join(memberships_fields, ","))
    |> Keyword.put(:include, updated_include)
  end
end