lib/mix/tasks/cf_sync.dump.ex

defmodule Mix.Tasks.CfSync.Dump do
  @moduledoc """
  This task downloads the contentful entries and assets to a local file for dev purposes.
  """
  @shortdoc "Dump CF data to .cf_sync/"

  use Mix.Task

  import CfSync.Tasks.Utils

  @impl Mix.Task
  def run(_args) do
    name =
      ask("Config name: (default) ",
        filter: ~r/^[a-z_]+$/,
        error_message: "Lowercase and underscore only, please.",
        default_value: "default"
      )

    case read_conf(name) do
      :no_file ->
        warn("No config data for \"#{name}\". Use Mix cf_sync.setup to create it.")

      {:ok, config} ->
        dump(name, config)
    end
  end

  defp dump(name, config) do
    data = fetch_all(config)
    write_dump!(name, data)
  end

  defp fetch_all(config) do
    HTTPoison.start()
    IO.puts("\n\nDownloading data...")

    url = initial_url(config.root_url, config.space_id)

    pages =
      fetch(url, config.delivery_token)
      |> Stream.iterate(&fetch_next(&1, config.delivery_token))
      |> Stream.take_while(&(&1 != nil))
      |> Stream.each(fn _ -> IO.write(".") end)
      |> Enum.to_list()

    IO.puts("\nDownloaded #{length(pages)} files.")

    pages
  end

  defp fetch_next(url, token) when is_binary(url),
    do: fetch(url, token)

  defp fetch_next(%{"nextPageUrl" => url}, token) when is_binary(url),
    do: fetch_next(url, token)

  defp fetch_next(_url, _token),
    do: nil

  defp fetch(url, token, attempts \\ 1)

  defp fetch(_url, _token, attempts) when attempts > 9 do
    raise "Unable to fetch data from Contentful"
  end

  defp fetch(url, token, attempts) do
    case CFSync.HTTPClient.HTTPoison.fetch(url, token) do
      {:ok, data} ->
        data

      {:rate_limited, delay} ->
        IO.puts("Rate limited...")
        Process.sleep(delay + 1000)
        fetch(url, token, attempts + 1)
    end
  end

  defp initial_url(root_url, space_id), do: "#{root_url}spaces/#{space_id}/sync/?initial=true"
end