lib/ex_the_one/client.ex

defmodule ExTheOne.Client do
  @moduledoc """
  The `ExTheOne.Client` module provides HTTP call and helper functions for using the API.
  """
  alias ExTheOne.Config
  alias ExTheOne.Struct.MovieResponse
  alias ExTheOne.Struct.QuoteResponse
  alias ExTheOne.Struct.ErrorResponse

  def list_movies do
    api_key = Config.api_key()
    url = "https://the-one-api.dev/v2/movie"

    req = Req.new()
    req = Req.Request.put_header(req, "authorization", "Bearer #{api_key}")

    res = Req.get!(req, url: url, decode_body: false)

    if res.status != 200 do
      error = decode_error_response(res)
      error_message = "Unabled to retrieve movies: " <> error.message
      {:error, error_message}
    else
      {:ok, movie_response} = decode_response(res, :movie)
      {:ok, movie_response.docs}
    end
  end

  def get_movie(movie_id) do
    api_key = Config.api_key()
    url = "https://the-one-api.dev/v2/movie/#{movie_id}"

    req = Req.new()
    req = Req.Request.put_header(req, "authorization", "Bearer #{api_key}")
    res = Req.get!(req, url: url, decode_body: false)

    if res.status != 200 do
      error = decode_error_response(res)
      error_message = "Unabled to retrieve movie_id: #{movie_id}. " <> error.message
      {:error, error_message}
    else
      {:ok, movie_response} = decode_response(res, :movie)
      {:ok, List.first(movie_response.docs)}
    end
  end

  def list_movie_quotes(movie_id) do
    api_key = Config.api_key()
    url = "https://the-one-api.dev/v2/movie/#{movie_id}/quote"

    req = Req.new()
    req = Req.Request.put_header(req, "authorization", "Bearer #{api_key}")
    res = Req.get!(req, url: url, decode_body: false)

    if res.status != 200 do
      error = decode_error_response(res)
      error_message = "Unabled to retrieve quotes from movie_id: #{movie_id}. " <> error.message
      {:error, error_message}
    else
      {:ok, movie_response} = decode_response(res, :quote)
      {:ok, movie_response.docs}
    end
  end

  def list_quotes do
    api_key = Config.api_key()
    base_url = "https://the-one-api.dev/v2"
    path = "/quote"
    url = base_url <> path
    params = [limit: 100]

    req = Req.new()
    req = Req.Request.put_header(req, "authorization", "Bearer #{api_key}")
    res = Req.get!(req, url: url, decode_body: false, params: params)

    if res.status != 200 do
      error = decode_error_response(res)
      error_message = "Unabled to retrieve quotes" <> error.message
      {:error, error_message}
    else
      {:ok, quote_response} = decode_response(res, :quote)
      results = quote_response.docs

      if quote_response.page < quote_response.pages do
        page_results(results, req, url, quote_response.page)
      else
        {:ok, results}
      end
    end
  end

  def get_quote(quote_id) do
    api_key = Config.api_key()
    url = "https://the-one-api.dev/v2/quote/#{quote_id}"

    req = Req.new()
    req = Req.Request.put_header(req, "authorization", "Bearer #{api_key}")
    res = Req.get!(req, url: url, decode_body: false)

    if res.status != 200 do
      error = decode_error_response(res)
      error_message = "Unabled to retrieve quote_id: #{quote_id}. " <> error.message
      {:error, error_message}
    else
      {:ok, quote_response} = decode_response(res, :quote)
      {:ok, List.first(quote_response.docs)}
    end
  end

  defp page_results(accumulated_results, req, url, page) do
    new_page = page + 1
    params = [limit: 100, page: new_page]

    res = Req.get!(req, url: url, decode_body: false, params: params)

    if res.status != 200 do
      error = decode_error_response(res)
      error_message = error.message <> " Unabled to retrieve quotes"
      {:error, error_message}
    else
      {:ok, quote_response} = decode_response(res, :quote)
      results = quote_response.docs

      accumulated_results = results ++ accumulated_results

      if quote_response.page < quote_response.pages do
        page_results(accumulated_results, req, url, quote_response.page)
      else
        {:ok, accumulated_results}
      end
    end
  end

  defp decode_response(res, type) do
    {:ok, data} = Jason.decode(res.body, keys: :atoms)

    case type do
      :movie ->
        MovieResponse.to_struct(data)

      :quote ->
        QuoteResponse.to_struct(data)
    end
  end

  defp decode_error_response(res) do
    {:ok, data} = Jason.decode(res.body, keys: :atoms)
    {:ok, error} = ErrorResponse.to_struct(data)
    error
  end
end