defmodule Post do
defstruct name: "john"
end
defmodule Terminator.UUID.Performer do
@moduledoc """
Performer is a main actor for determining abilities
"""
use Terminator.UUID.Schema
import Ecto.Changeset
import Ecto.Query
alias __MODULE__
alias Terminator.UUID.Ability
alias Terminator.UUID.Repo
alias Terminator.UUID.Role
alias Terminator.UUID.PerformersEntities
alias Terminator.UUID.PerformersRoles
@typedoc "A performer struct"
@type t :: %Performer{}
schema "terminator_uuid_performers" do
field(:abilities, {:array, :string}, default: [])
many_to_many(:roles, Role, join_through: PerformersRoles)
has_many(:entities, PerformersEntities)
timestamps()
end
def changeset(%Performer{} = struct, params \\ %{}) do
struct
|> cast(params, [])
end
@doc """
Grant given grant type to a performer.
## Examples
Function accepts either `Terminator.UUID.Ability` or `Terminator.UUID.Role` grants.
Function is merging existing grants with the new ones, so calling grant with same
grants will not duplicate entries in table.
To grant particular ability to a given performer
iex> Terminator.UUID.Performer.grant(%Terminator.UUID.Performer{id: 1}, %Terminator.UUID.Ability{id: 1})
To grant particular role to a given performer
iex> Terminator.UUID.Performer.grant(%Terminator.UUID.Performer{id: 1}, %Terminator.UUID.Role{id: 1})
"""
@spec grant(Performer.t(), Ability.t() | Role.t()) :: Performer.t()
def grant(%Performer{id: id} = _performer, %Role{id: _id} = role) do
# Preload performer roles
performer = Performer |> Repo.get!(id) |> Repo.preload([:roles])
roles = merge_uniq_grants(performer.roles ++ [role])
changeset =
changeset(performer)
|> put_assoc(:roles, roles)
changeset |> Repo.update!()
end
def grant(%{performer: %Performer{id: _pid} = performer}, %Role{id: _id} = role) do
grant(performer, role)
end
def grant(%{performer_id: id}, %Role{id: _id} = role) do
performer = Performer |> Repo.get!(id)
grant(performer, role)
end
def grant(%Performer{id: id} = _performer, %Ability{id: _id} = ability) do
performer = Performer |> Repo.get!(id)
abilities = Enum.uniq(performer.abilities ++ [ability.identifier])
changeset =
changeset(performer)
|> put_change(:abilities, abilities)
changeset |> Repo.update!()
end
def grant(%{performer: %Performer{id: id}}, %Ability{id: _id} = ability) do
performer = Performer |> Repo.get!(id)
grant(performer, ability)
end
def grant(%{performer_id: id}, %Ability{id: _id} = ability) do
performer = Performer |> Repo.get!(id)
grant(performer, ability)
end
def grant(_, _), do: raise(ArgumentError, message: "Bad arguments for giving grant")
def grant(
%Performer{id: _pid} = performer,
%Ability{id: _aid} = ability,
%{__struct__: _entity_name, id: _entity_id} = entity
) do
entity_abilities = load_performer_entities(performer, entity)
case entity_abilities do
nil ->
PerformersEntities.create(performer, entity, [ability.identifier])
entity ->
abilities = Enum.uniq(entity.abilities ++ [ability.identifier])
PerformersEntities.changeset(entity)
|> put_change(:abilities, abilities)
|> Repo.update!()
end
performer
end
def grant(
%{performer_id: id},
%Ability{id: _id} = ability,
%{__struct__: _entity_name, id: _entity_id} = entity
) do
grant(%Performer{id: id}, ability, entity)
end
def grant(
%{performer: %Performer{id: _pid} = performer},
%Ability{id: _id} = ability,
%{__struct__: _entity_name, id: _entity_id} = entity
) do
grant(performer, ability, entity)
end
def grant(_, _, _), do: raise(ArgumentError, message: "Bad arguments for giving grant")
@doc """
Revoke given grant type from a performer.
## Examples
Function accepts either `Terminator.UUID.Ability` or `Terminator.UUID.Role` grants.
Function is directly opposite of `Terminator.UUID.Performer.grant/2`
To revoke particular ability from a given performer
iex> Terminator.UUID.Performer.revoke(%Terminator.UUID.Performer{id: 1}, %Terminator.UUID.Ability{id: 1})
To revoke particular role from a given performer
iex> Terminator.UUID.Performer.revoke(%Terminator.UUID.Performer{id: 1}, %Terminator.UUID.Role{id: 1})
"""
@spec revoke(Performer.t(), Ability.t() | Role.t()) :: Performer.t()
def revoke(%Performer{id: id} = _performer, %Role{id: _id} = role) do
from(pa in PerformersRoles)
|> where([pr], pr.performer_id == ^id and pr.role_id == ^role.id)
|> Repo.delete_all()
end
def revoke(%{performer: %Performer{id: _pid} = performer}, %Role{id: _id} = role) do
revoke(performer, role)
end
def revoke(%{performer_id: id}, %Role{id: _id} = role) do
revoke(%Performer{id: id}, role)
end
def revoke(%Performer{id: id} = _performer, %Ability{id: _id} = ability) do
performer = Performer |> Repo.get!(id)
abilities =
Enum.filter(performer.abilities, fn grant ->
grant != ability.identifier
end)
changeset =
changeset(performer)
|> put_change(:abilities, abilities)
changeset |> Repo.update!()
end
def revoke(
%{performer: %Performer{id: _pid} = performer},
%Ability{id: _id} = ability
) do
revoke(performer, ability)
end
def revoke(%{performer_id: id}, %Ability{id: _id} = ability) do
revoke(%Performer{id: id}, ability)
end
def revoke(_, _), do: raise(ArgumentError, message: "Bad arguments for revoking grant")
def revoke(
%Performer{id: _pid} = performer,
%Ability{id: _id} = ability,
%{__struct__: _entity_name, id: _entity_id} = entity
) do
entity_abilities = load_performer_entities(performer, entity)
case entity_abilities do
nil ->
performer
entity ->
abilities =
Enum.filter(entity.abilities, fn grant ->
grant != ability.identifier
end)
if length(abilities) == 0 do
entity |> Repo.delete!()
else
PerformersEntities.changeset(entity)
|> put_change(:abilities, abilities)
|> Repo.update!()
end
performer
end
end
def revoke(
%{performer_id: id},
%Ability{id: _id} = ability,
%{__struct__: _entity_name, id: _entity_id} = entity
) do
revoke(%Performer{id: id}, ability, entity)
end
def revoke(
%{performer: %Performer{id: _pid} = performer},
%Ability{id: _id} = ability,
%{__struct__: _entity_name, id: _entity_id} = entity
) do
revoke(performer, ability, entity)
end
def revoke(_, _, _), do: raise(ArgumentError, message: "Bad arguments for revoking grant")
def load_performer_entities(performer, %{__struct__: entity_name, id: entity_id}) do
PerformersEntities
|> where(
[e],
e.performer_id == ^performer.id and e.assoc_id == ^entity_id and
e.assoc_type == ^PerformersEntities.normalize_struct_name(entity_name)
)
|> Repo.one()
end
def table, do: :terminator_uuid_performers
defp merge_uniq_grants(grants) do
Enum.uniq_by(grants, fn grant ->
grant.identifier
end)
end
end