lib/mix/tasks/arke.export_data.ex

defmodule Mix.Tasks.Arke.ExportData do
  @moduledoc """
  Export data from a given project in the database and save them in json file.
    It exports, by default, all the arkes,groups, parameters and permissions

  ## Examples

      $ mix arke.export_data --project my_project1 --p myproject2
      $ mix arke.export_data --project my_project --arke

  ## Command line options

  * `--project` - The id of the project used to export data.
  * `--arke` - Export only the arkes
  * `--group` - Export only the groups
  * `--parameter` - Export only the parameters
  * `--split_file` - Write different files for arkes,groups,parameters and links
  * `--persistence` - The persistence to use. One of:
      * `arke_postgres` - via https://github.com/elixir-ecto/postgrex (Default)
  """

  use Mix.Task
  alias Arke.QueryManager
  alias Arke.LinkManager
  alias Arke.Utils.ErrorGenerator, as: Error
  alias Arke.Boundary.{ArkeManager}

  alias Arke.Core.Unit
  @decode_keys [:arke, :parameter, :group, :link]
  @shortdoc "Export data from a project"
  @persistence_repo ["arke_postgres"]

  @switches [
    project: :string,
    arke: :boolean,
    parameter: :boolean,
    splitfile: :boolean,
    group: :boolean,
    persistence: :string
  ]
  @aliases [
    p: :project,
    a: :arke,
    g: :group,
    sf: :splitfile,
    pr: :parameter,
    ps: :persistence
  ]

  @impl true
  def run(args) do
    case OptionParser.parse!(args, strict: @switches, aliases: @aliases) do
      {[], _opts} ->
        Mix.Tasks.Help.run(["arke.export_data"])

      {opts, []} ->
        persistence = parse_persistence!(opts[:persistence] || "arke_postgres")

        (app_to_start(persistence) ++ [:arke])
        |> Enum.each(&Application.ensure_all_started/1)

        repo_module = Application.get_env(:arke, :persistence)[String.to_atom(persistence)][:repo]
        Mix.shell().info("--- Starting repo --- ")

        case start_repo!(repo_module) do
          {:ok, pid} ->
            opts |> export_data(persistence)
            Process.exit(pid, :normal)
            :ok

          {:error, _} ->
            opts |> export_data(persistence)
            :ok
        end
    end
  end

  defp app_to_start("arke_postgres"), do: [:ecto_sql, :postgrex]
  defp parse_persistence!(ps) when ps in @persistence_repo, do: ps

  defp parse_persistence!(ps),
    do:
      Mix.raise(
        "Invalid persistence: `#{ps}`\nSupported persistence are: #{Enum.join(@persistence_repo, " | ")}"
      )

  defp write_to_file(project, arke_id, data) do
    {:ok, datetime} =
      Arke.Utils.DatetimeHandler.now(:datetime)
      |> Arke.Utils.DatetimeHandler.format("{ISO:Basic:Z}")

    dir_path = "export/arke_export_data/#{project}/#{datetime}"
    arke_id_str = to_string(arke_id)
    path = "#{dir_path}/#{arke_id_str}.json"
    Mix.shell().info("--- Writing data to #{path} for #{arke_id_str}  --- ")
    File.mkdir_p!(dir_path)
    {:ok, body} = Jason.encode(data)
    {:ok, file} = File.open(path, [:append, :utf8])
    IO.write(file, body)
    File.close(file)
  end

  defp start_repo!(nil),
    do:
      Mix.raise(
        "Invalid repo module in arke configuration. Please provide a valid module accordingly to the persistence supported"
      )

  # this is for arke_postgres
  defp start_repo!(repo_module) do
    repo_module.start_link()
  end

  defp start_manager!(nil),
    do:
      Mix.raise(
        "Missing `init` function in arke.persistence configuration. Please provide a valid function accordingly to the persistence supported"
      )

  # this is for arke_postgres
  defp start_manager!(function), do: function.()

  defp export_data(opts, persistence) do
    start_manager!(Application.get_env(:arke, :persistence)[String.to_atom(persistence)][:init])
    project = String.to_atom(opts[:project]) || :arke_system
    split_file = opts[:splitfile] || false
    export_data = Arke.Utils.Export.get_db_structure(project, opts)

    if split_file do
      write_to_file(project, :arke, %{arke: export_data.arke})
      write_to_file(project, :group, %{group: export_data.group})
      write_to_file(project, :parameter, %{parameter: export_data.parameter})
      write_to_file(project, :link, %{link: export_data.link})
    else
      write_to_file(project, :all, export_data)
    end

    Mix.shell().info(
      "--- All data has been exported. Keep in mind that if you want to use them in the `arke.seed_project` task
       you must move them under the `registry` folder --- "
    )
  end
end