defmodule Auth.Role do
@moduledoc """
Defines roles schema and CRUD functions
"""
use Ecto.Schema
import Ecto.Changeset
import Ecto.Query, warn: false
alias Auth.Repo
# https://stackoverflow.com/a/47501059/1148249
alias __MODULE__
schema "roles" do
field :desc, :string
field :name, :string
field :person_id, :id
field :app_id, :id
# many_to_many :roles, Auth.Role, join_through: Auth.PeopleRoles
timestamps()
end
@doc false
def changeset(role, attrs) do
role
|> cast(attrs, [:name, :desc, :person_id, :app_id])
|> validate_required([:name, :desc])
|> validate_role_name(attrs)
end
# don't allow role.name to be the same as any default role to avoid
# role/priviledge escalation: https://github.com/dwyl/auth/issues/118
defp validate_role_name(changeset, attrs) do
app_id = Map.get(attrs, :app_id, 1)
existing_role_names = existing_role_names_list(app_id)
validate_change(changeset, :name, fn :name, name ->
if Enum.member?(existing_role_names, name) do
[name: "Sorry, role name cannot be #{name} as it already exists"]
else
[]
end
end)
end
# Note: this does not prevent different apps from having roles
# with the same name e.g. "kitten lover" could be in every app
# fetch a list of the roles for the given app:
defp existing_role_names_list(app_id) do
# get roles for "system" app (app.id=1)
Enum.map(list_roles_for_app(app_id), fn r -> r.name end)
end
@doc """
Returns the list of roles.
## Examples
iex> list_roles()
[%Role{}, ...]
"""
def list_roles do
Repo.all(__MODULE__)
end
# returns all roles including default roles for a given app
def list_roles_for_app(app_id) do
__MODULE__
# and a.status != 6)
# select roles for app_id and default roles (without app_id):
|> where([r], r.app_id == ^app_id or is_nil(r.app_id))
|> Repo.all()
end
def list_role_ids_for_app(app_id) do
roles = list_roles_for_app(app_id)
Enum.map(roles, fn r -> to_string(r.id) end)
end
@doc """
get all roles for apps owned + default roles
"""
def list_roles_for_apps(app_ids) do
__MODULE__
# and r.status != 6
|> where([r], r.app_id in ^app_ids or is_nil(r.app_id))
|> Repo.all()
end
@doc """
Gets a single role.
Raises `Ecto.NoResultsError` if the Role does not exist.
## Examples
iex> get_role!(123)
%Role{}
iex> get_role!(456)
** (Ecto.NoResultsError)
"""
def get_role!(id), do: Repo.get!(__MODULE__, id)
def get_role!(id, person_id) do
# IO.inspect(id, label: "id")
# IO.inspect(person_id, label: "person_id")
__MODULE__
|> where([r], r.id == ^id and r.person_id == ^person_id)
|> Repo.one()
# |> IO.inspect()
end
@doc """
Creates a role.
## Examples
iex> create_role(%{field: value})
{:ok, %Role{}}
iex> create_role(%{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def create_role(attrs \\ %{}) do
%Role{}
|> Role.changeset(attrs)
|> Repo.insert()
end
@doc """
Updates a role.
## Examples
iex> update_role(role, %{field: new_value})
{:ok, %Role{}}
iex> update_role(role, %{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def update_role(%Role{} = role, attrs) do
role
|> Role.changeset(attrs)
|> Repo.update()
end
def upsert_role(role) do
id = Map.get(role, :id)
# if the role Map has no "id" field its not a DB record
if is_nil(id) do
create_role(role)
else
case Repo.get_by(__MODULE__, id: id) do
# record does not exist so create it:
nil ->
create_role(role)
# record exists, lets update it:
existing_role ->
merged = Map.merge(strip_meta(existing_role), strip_meta(role))
update_role(existing_role, merged)
end
end
end
def strip_meta(struct) do
struct
|> Map.delete(:__meta__)
|> Map.delete(:__struct__)
end
@doc """
Deletes a role.
## Examples
iex> delete_role(role)
{:ok, %Role{}}
iex> delete_role(role)
{:error, %Ecto.Changeset{}}
"""
def delete_role(%Role{} = role) do
Repo.delete(role)
end
@doc """
Returns an `%Ecto.Changeset{}` for tracking role changes.
## Examples
iex> change_role(role)
%Ecto.Changeset{data: %Role{}}
"""
def change_role(%Role{} = role, attrs \\ %{}) do
Role.changeset(role, attrs)
end
# @doc """
# grants the default "subscriber" (6) role to the person
# """
# def set_default_role(person) do
# end
end