lib/database/dependency.ex

defmodule MishkaInstaller.Dependency do
  @moduledoc """

  This module can hold your dependencies' information in database

  > Do not use these functions directly, because we do not update JSON file in this module, some functions were prepared in DepHandler module for you.
  """
  alias MishkaInstaller.Database.DependencySchema
  alias MishkaInstaller.Installer.DepHandler
  import Ecto.Query

  use MishkaDeveloperTools.DB.CRUD,
    module: DependencySchema,
    error_atom: :dependency,
    repo: MishkaInstaller.repo()

  @behaviour MishkaDeveloperTools.DB.CRUD

  @doc delegate_to: {MishkaDeveloperTools.DB.CRUD, :crud_add, 1}
  def create(attrs) do
    crud_add(attrs)
    |> notify_subscribers(:dependency)
  end

  @doc delegate_to: {MishkaDeveloperTools.DB.CRUD, :crud_add, 1}
  def create(attrs, allowed_fields) do
    crud_add(attrs, allowed_fields)
    |> notify_subscribers(:dependency)
  end

  @doc delegate_to: {MishkaDeveloperTools.DB.CRUD, :crud_edit, 1}
  def edit(attrs) do
    crud_edit(attrs)
    |> notify_subscribers(:dependency)
  end

  @doc delegate_to: {MishkaDeveloperTools.DB.CRUD, :crud_edit, 1}
  def edit(attrs, allowed_fields) do
    crud_edit(attrs, allowed_fields)
    |> notify_subscribers(:dependency)
  end

  @doc delegate_to: {MishkaDeveloperTools.DB.CRUD, :crud_delete, 1}
  def delete(id) do
    crud_delete(id)
    |> notify_subscribers(:dependency)
  end

  @doc delegate_to: {MishkaDeveloperTools.DB.CRUD, :crud_get_record, 1}
  def show_by_id(id) do
    crud_get_record(id)
  end

  @doc delegate_to: {MishkaDeveloperTools.DB.CRUD, :crud_get_by_field, 2}
  def show_by_name(app) do
    crud_get_by_field("app", app)
  end

  def update_app_version(id, data) do
    crud_edit(
      Map.merge(convert_map_from_atom_to_string(data), %{
        "id" => id,
        "dependency_type" => "force_update"
      })
    )
  end

  @spec create_or_update(map()) :: tuple()
  def create_or_update(data) do
    case show_by_name(data.app) do
      {:error, :get_record_by_field, :dependency} ->
        create(data)

      {:ok, :get_record_by_field, :dependency, record_info} ->
        if Version.compare(data.version, record_info.version) == :gt do
          update_app_version(record_info.id, data)
        else
          {:error, :update_app_version, :older_version}
        end
    end
  end

  def dependencies() do
    from(dep in DependencySchema)
    |> fields()
    |> MishkaInstaller.repo().all()
  end

  def dependencies(:struct) do
    dependencies()
    |> Enum.map(&struct(DepHandler, &1))
  end

  @spec change_dependency_type_with_app(String.t(), String.t()) ::
          {:ok, :change_dependency_type_with_app, map()}
          | {:error, :change_dependency_type_with_app, :dependency, atom() | map()}
  def change_dependency_type_with_app(app, dependency_type) do
    with {:ok, :get_record_by_field, :dependency, record_info} <-
           MishkaInstaller.Dependency.show_by_name(app),
         {:ok, :edit, :dependency, repo_data} <-
           MishkaInstaller.Dependency.edit(%{
             "id" => record_info.id,
             "dependency_type" => dependency_type
           }) do
      {:ok, :change_dependency_type_with_app, repo_data}
    else
      {:error, :get_record_by_field, :dependency} ->
        {:error, :change_dependency_type_with_app, :dependency, :not_found}

      {:error, :edit, action, :dependency} when action in [:uuid, :get_record_by_id] ->
        {:error, :change_dependency_type_with_app, :dependency, :not_found}

      {:error, :edit, :dependency, repo_error} ->
        {:error, :change_dependency_type_with_app, :dependency, repo_error}
    end
  end

  defp fields(query) do
    from([dep] in query,
      order_by: [desc: dep.inserted_at, desc: dep.id],
      select: %{
        app: dep.app,
        version: dep.version,
        type: dep.type,
        url: dep.url,
        git_tag: dep.git_tag,
        custom_command: dep.custom_command,
        dependency_type: dep.dependency_type,
        dependencies: dep.dependencies
      }
    )
  end

  def subscribe do
    Phoenix.PubSub.subscribe(
      MishkaInstaller.get_config(:pubsub) || MishkaInstaller.PubSub,
      "dependency"
    )
  end

  if Mix.env() == :test do
    def notify_subscribers(params, _type_send), do: params
  else
    def notify_subscribers({:ok, action, :dependency, repo_data} = params, type_send) do
      Phoenix.PubSub.broadcast(
        MishkaInstaller.get_config(:pubsub) || MishkaInstaller.PubSub,
        "dependency",
        {:ok, type_send, action, repo_data}
      )

      params
    end

    def notify_subscribers(params, _), do: params
  end

  defp convert_map_from_atom_to_string(map) do
    for {key, val} <- map, into: %{} do
      {to_string(key), to_string(val)}
    end
  end
end