lib/database/setting.ex

defmodule MishkaInstaller.Setting do
  @moduledoc """
  This module is for communication with `Settings` table and has essential functions such as
  adding, editing, deleting, and displaying.
  This module is related to module `MishkaInstaller.Database.SettingSchema`.
  """

  alias MishkaInstaller.Database.SettingSchema
  import Ecto.Query

  use MishkaDeveloperTools.DB.CRUD,
    module: SettingSchema,
    error_atom: :setting,
    repo: MishkaInstaller.repo()

  @behaviour MishkaDeveloperTools.DB.CRUD

  @doc """
  If you want to get the latest changes from the `Settings` table of your database,
  this function can help you to be subscribed.
  """
  def subscribe do
    Phoenix.PubSub.subscribe(
      MishkaInstaller.get_config(:pubsub) || MishkaInstaller.PubSub,
      "setting"
    )
  end

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

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

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

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

  @doc delegate_to: {MishkaDeveloperTools.DB.CRUD, :crud_delete, 1}
  def delete(id) do
    crud_delete(id)
    |> notify_subscribers(:setting)
  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(name) do
    crud_get_by_field("name", name)
  end

  @doc """
  This is an aggregation function that includes editing or adding by name.
  """
  def add_or_edit_by_name(data) do
    case show_by_name("#{data.name}") do
      {:ok, :get_record_by_field, :setting, repo_data} ->
        edit(data |> Map.merge(%{id: repo_data.id}))

      _ ->
        create(data)
    end
  end

  @doc """
  Show all settings.
  """
  @spec settings([
          {:conditions, {String.t() | integer(), String.t() | integer()}} | {:filters, map()},
          ...
        ]) :: any
  def settings(conditions: {page, page_size}, filters: filters) do
    try do
      query = from(set in SettingSchema) |> convert_filters_to_where(filters)

      from([set] in query,
        order_by: [desc: set.inserted_at, desc: set.id],
        select: %{
          id: set.id,
          name: set.name,
          configs: set.configs,
          updated_at: set.updated_at,
          inserted_at: set.inserted_at
        }
      )
      |> MishkaInstaller.repo().paginate(page: page, page_size: page_size)
    rescue
      _db_error ->
        case Code.ensure_compiled(module = Scrivener.Page) do
          {:module, _} ->
            struct(module, %{
              entries: [],
              page_number: 1,
              page_size: page_size,
              total_entries: 0,
              total_pages: 1
            })

          {:error, _} ->
            %{entries: [], page_number: 1, page_size: page_size, total_entries: 0, total_pages: 1}
        end
    end
  end

  def settings(filters: filters) do
    try do
      query = from(set in SettingSchema) |> convert_filters_to_where(filters)

      from([set] in query,
        order_by: [desc: set.inserted_at, desc: set.id],
        select: %{
          id: set.id,
          name: set.name,
          configs: set.configs,
          updated_at: set.updated_at,
          inserted_at: set.inserted_at
        }
      )
      |> MishkaInstaller.repo().all()
    rescue
      _db_error -> []
    end
  end

  def settings() do
    from(plg in SettingSchema)
    |> fields()
    |> MishkaInstaller.repo().all()
  end

  defp convert_filters_to_where(query, filters) do
    Enum.reduce(filters, query, fn {key, value}, query ->
      from(set in query, where: field(set, ^key) == ^value)
    end)
  end

  defp fields(query) do
    from([set] in query,
      order_by: [desc: set.inserted_at, desc: set.id],
      select: %{
        name: set.name,
        configs: set.configs
      }
    )
  end

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

      params
    end

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

  @spec allowed_fields(:atom | :string) :: nil | list
  def allowed_fields(:atom), do: SettingSchema.__schema__(:fields)

  def allowed_fields(:string),
    do: SettingSchema.__schema__(:fields) |> Enum.map(&Atom.to_string/1)
end