lib/glific/access_control/trigger_role.ex

defmodule Glific.AccessControl.TriggerRole do
  @moduledoc """
  A pipe for managing the role triggers
  """
  use Ecto.Schema
  import Ecto.Changeset
  import Ecto.Query, warn: false

  alias __MODULE__

  alias Glific.{
    AccessControl.Role,
    AccessControl.UserRole,
    Partners.Organization,
    Repo,
    Triggers.Trigger,
    Users.User
  }

  use Ecto.Schema
  import Ecto.Changeset

  @required_fields [:role_id, :trigger_id, :organization_id]

  @type t() :: %__MODULE__{
          __meta__: Ecto.Schema.Metadata.t(),
          id: non_neg_integer | nil,
          role: Role.t() | Ecto.Association.NotLoaded.t() | nil,
          trigger: Trigger.t() | Ecto.Association.NotLoaded.t() | nil,
          organization_id: non_neg_integer | nil,
          organization: Organization.t() | Ecto.Association.NotLoaded.t() | nil
        }

  schema "trigger_roles" do
    belongs_to :role, Role
    belongs_to :trigger, Trigger
    belongs_to :organization, Organization
  end

  @doc """
  Standard changeset pattern we use for all data types
  """
  @spec changeset(TriggerRole.t(), map()) :: Ecto.Changeset.t()
  def changeset(role_trigger, attrs) do
    role_trigger
    |> cast(attrs, @required_fields)
    |> validate_required(@required_fields)
    |> unique_constraint(@required_fields)
    |> foreign_key_constraint(:role_id)
    |> foreign_key_constraint(:trigger_id)
  end

  @doc """
  Creates a access control.
  ## Examples
      iex> create_trigger_role(%{field: value})
      {:ok, %TriggerRole{}}
      iex> create_trigger_role(%{field: bad_value})
      {:error, %Ecto.Changeset{}}
  """
  @spec create_trigger_role(map()) :: {:ok, TriggerRole.t()} | {:error, Ecto.Changeset.t()}
  def create_trigger_role(attrs \\ %{}) do
    %TriggerRole{}
    |> changeset(attrs)
    |> Repo.insert()
  end

  @doc """
  Update trigger roles based on add_role_ids and delete_role_ids and return number_deleted as integer and roles added as access_controls
  """
  @spec update_trigger_roles(map()) :: map()
  def update_trigger_roles(
        %{
          trigger_id: trigger_id,
          add_role_ids: add_role_ids
        } = attrs
      ) do
    access_controls =
      Enum.reduce(
        add_role_ids,
        [],
        fn role_id, acc ->
          case create_trigger_role(Map.merge(attrs, %{role_id: role_id, trigger_id: trigger_id})) do
            {:ok, access_control} -> [access_control | acc]
            _ -> acc
          end
        end
      )

    {number_deleted, _} =
      if Map.has_key?(attrs, :delete_role_ids),
        do: delete_trigger_roles_by_role_ids(trigger_id, attrs.delete_role_ids),
        else: {0, attrs}

    %{
      number_deleted: number_deleted,
      access_controls: access_controls
    }
  end

  @doc """
  Delete trigger roles
  """
  @spec delete_trigger_roles_by_role_ids(integer, list()) :: {integer(), nil | [term()]}
  def delete_trigger_roles_by_role_ids(trigger_id, role_ids) do
    fields = {{:trigger_id, trigger_id}, {:role_id, role_ids}}
    Repo.delete_relationships_by_ids(TriggerRole, fields)
  end

  @doc """
  Filtering entity object based on user role
  """
  @spec check_access(Ecto.Query.t(), User.t()) :: Ecto.Query.t()
  def check_access(entity_list, user) do
    sub_query =
      TriggerRole
      |> select([rf], rf.trigger_id)
      |> join(:inner, [rf], ru in UserRole, as: :ru, on: ru.role_id == rf.role_id)
      |> where([rf, ru: ru], ru.user_id == ^user.id)

    entity_list
    |> where(
      [f],
      f.id in subquery(sub_query)
    )
  end
end