feat: add Role resource with validations
Create Role resource with name, description, permission_set_name, and is_system_role fields. Add validations for permission_set_name and system role deletion protection.
This commit is contained in:
parent
1b2927ce40
commit
4535551b8d
1 changed files with 152 additions and 0 deletions
152
lib/mv/authorization/role.ex
Normal file
152
lib/mv/authorization/role.ex
Normal file
|
|
@ -0,0 +1,152 @@
|
||||||
|
defmodule Mv.Authorization.Role do
|
||||||
|
@moduledoc """
|
||||||
|
Represents a user role that references a permission set.
|
||||||
|
|
||||||
|
Roles are stored in the database and link users to permission sets.
|
||||||
|
Each role has a `permission_set_name` that references one of the four
|
||||||
|
hardcoded permission sets defined in `Mv.Authorization.PermissionSets`.
|
||||||
|
|
||||||
|
## Fields
|
||||||
|
|
||||||
|
- `name` - Unique role name (e.g., "Vorstand", "Admin")
|
||||||
|
- `description` - Human-readable description of the role
|
||||||
|
- `permission_set_name` - Must be one of: "own_data", "read_only", "normal_user", "admin"
|
||||||
|
- `is_system_role` - If true, role cannot be deleted (protects critical roles like "Mitglied")
|
||||||
|
|
||||||
|
## Relationships
|
||||||
|
|
||||||
|
- `has_many :users` - Users assigned to this role
|
||||||
|
|
||||||
|
## Validations
|
||||||
|
|
||||||
|
- `permission_set_name` must be a valid permission set (checked against PermissionSets.all_permission_sets/0)
|
||||||
|
- `name` must be unique
|
||||||
|
- System roles cannot be deleted (enforced via validation)
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
# Create a new role
|
||||||
|
{:ok, role} = Mv.Authorization.create_role(%{
|
||||||
|
name: "Vorstand",
|
||||||
|
description: "Board member with read access",
|
||||||
|
permission_set_name: "read_only"
|
||||||
|
})
|
||||||
|
|
||||||
|
# List all roles
|
||||||
|
{:ok, roles} = Mv.Authorization.list_roles()
|
||||||
|
"""
|
||||||
|
use Ash.Resource,
|
||||||
|
domain: Mv.Authorization,
|
||||||
|
data_layer: AshPostgres.DataLayer
|
||||||
|
|
||||||
|
postgres do
|
||||||
|
table "roles"
|
||||||
|
repo Mv.Repo
|
||||||
|
end
|
||||||
|
|
||||||
|
code_interface do
|
||||||
|
define :create_role
|
||||||
|
define :list_roles, action: :read
|
||||||
|
define :update_role
|
||||||
|
define :destroy_role, action: :destroy
|
||||||
|
end
|
||||||
|
|
||||||
|
actions do
|
||||||
|
defaults [:read]
|
||||||
|
|
||||||
|
create :create_role do
|
||||||
|
primary? true
|
||||||
|
accept [:name, :description, :permission_set_name, :is_system_role]
|
||||||
|
# Note: In Ash 3.0, require_atomic? is not available for create actions
|
||||||
|
# Custom validations will still work
|
||||||
|
end
|
||||||
|
|
||||||
|
update :update_role do
|
||||||
|
primary? true
|
||||||
|
accept [:name, :description, :permission_set_name, :is_system_role]
|
||||||
|
# Required because custom validation functions cannot be executed atomically
|
||||||
|
require_atomic? false
|
||||||
|
end
|
||||||
|
|
||||||
|
destroy :destroy do
|
||||||
|
# Required because custom validation functions cannot be executed atomically
|
||||||
|
require_atomic? false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
validations do
|
||||||
|
validate fn changeset, _context ->
|
||||||
|
permission_set_name = Ash.Changeset.get_attribute(changeset, :permission_set_name)
|
||||||
|
|
||||||
|
if permission_set_name do
|
||||||
|
valid_sets =
|
||||||
|
Mv.Authorization.PermissionSets.all_permission_sets()
|
||||||
|
|> Enum.map(&Atom.to_string/1)
|
||||||
|
|
||||||
|
if permission_set_name in valid_sets do
|
||||||
|
:ok
|
||||||
|
else
|
||||||
|
valid_sets_string = Enum.join(valid_sets, ", ")
|
||||||
|
|
||||||
|
{:error,
|
||||||
|
field: :permission_set_name,
|
||||||
|
message: "Invalid permission set name. Must be one of: #{valid_sets_string}"}
|
||||||
|
end
|
||||||
|
else
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
validate fn changeset, _context ->
|
||||||
|
if changeset.action_type == :destroy do
|
||||||
|
if changeset.data.is_system_role do
|
||||||
|
{:error,
|
||||||
|
message:
|
||||||
|
"Cannot delete system role. System roles are required for the application to function."}
|
||||||
|
else
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
else
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
on: [:destroy]
|
||||||
|
end
|
||||||
|
|
||||||
|
attributes do
|
||||||
|
uuid_primary_key :id
|
||||||
|
|
||||||
|
attribute :name, :string do
|
||||||
|
allow_nil? false
|
||||||
|
public? true
|
||||||
|
end
|
||||||
|
|
||||||
|
attribute :description, :string do
|
||||||
|
allow_nil? true
|
||||||
|
public? true
|
||||||
|
end
|
||||||
|
|
||||||
|
attribute :permission_set_name, :string do
|
||||||
|
allow_nil? false
|
||||||
|
public? true
|
||||||
|
end
|
||||||
|
|
||||||
|
attribute :is_system_role, :boolean do
|
||||||
|
allow_nil? false
|
||||||
|
default false
|
||||||
|
public? true
|
||||||
|
end
|
||||||
|
|
||||||
|
timestamps()
|
||||||
|
end
|
||||||
|
|
||||||
|
relationships do
|
||||||
|
has_many :users, Mv.Accounts.User do
|
||||||
|
destination_attribute :role_id
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
identities do
|
||||||
|
identity :unique_name, [:name]
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Add table
Add a link
Reference in a new issue