diff --git a/lib/accounts/user.ex b/lib/accounts/user.ex index 655dcc6..ceedeae 100644 --- a/lib/accounts/user.ex +++ b/lib/accounts/user.ex @@ -17,6 +17,10 @@ defmodule Mv.Accounts.User do # When a member is deleted, set the user's member_id to NULL # This allows users to continue existing even if their linked member is removed reference :member, on_delete: :nilify + + # When a role is deleted, prevent deletion if users are assigned to it + # This protects critical roles from accidental deletion + reference :role, on_delete: :restrict end end diff --git a/lib/mv/authorization/role.ex b/lib/mv/authorization/role.ex index ff1f2c1..da43510 100644 --- a/lib/mv/authorization/role.ex +++ b/lib/mv/authorization/role.ex @@ -61,14 +61,16 @@ defmodule Mv.Authorization.Role do create :create_role do primary? true - accept [:name, :description, :permission_set_name, :is_system_role] + # is_system_role is intentionally excluded - should only be set via seeds/internal actions + accept [:name, :description, :permission_set_name] # 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] + # is_system_role is intentionally excluded - should only be set via seeds/internal actions + accept [:name, :description, :permission_set_name] # Required because custom validation functions cannot be executed atomically require_atomic? false end @@ -85,7 +87,8 @@ defmodule Mv.Authorization.Role do Mv.Authorization.PermissionSets.all_permission_sets() |> Enum.map(&Atom.to_string/1) ), - message: "must be one of: own_data, read_only, normal_user, admin" + message: + "must be one of: #{Mv.Authorization.PermissionSets.all_permission_sets() |> Enum.map_join(", ", &Atom.to_string/1)}" validate fn changeset, _context -> if changeset.data.is_system_role do diff --git a/priv/resource_snapshots/repo/roles/20260106161215.json b/priv/resource_snapshots/repo/roles/20260106161215.json deleted file mode 100644 index 78c5636..0000000 --- a/priv/resource_snapshots/repo/roles/20260106161215.json +++ /dev/null @@ -1,118 +0,0 @@ -{ - "attributes": [ - { - "allow_nil?": false, - "default": "fragment(\"gen_random_uuid()\")", - "generated?": false, - "precision": null, - "primary_key?": true, - "references": null, - "scale": null, - "size": null, - "source": "id", - "type": "uuid" - }, - { - "allow_nil?": false, - "default": "nil", - "generated?": false, - "precision": null, - "primary_key?": false, - "references": null, - "scale": null, - "size": null, - "source": "name", - "type": "text" - }, - { - "allow_nil?": true, - "default": "nil", - "generated?": false, - "precision": null, - "primary_key?": false, - "references": null, - "scale": null, - "size": null, - "source": "description", - "type": "text" - }, - { - "allow_nil?": false, - "default": "nil", - "generated?": false, - "precision": null, - "primary_key?": false, - "references": null, - "scale": null, - "size": null, - "source": "permission_set_name", - "type": "text" - }, - { - "allow_nil?": false, - "default": "false", - "generated?": false, - "precision": null, - "primary_key?": false, - "references": null, - "scale": null, - "size": null, - "source": "is_system_role", - "type": "boolean" - }, - { - "allow_nil?": false, - "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", - "generated?": false, - "precision": null, - "primary_key?": false, - "references": null, - "scale": null, - "size": null, - "source": "inserted_at", - "type": "utc_datetime_usec" - }, - { - "allow_nil?": false, - "default": "fragment(\"(now() AT TIME ZONE 'utc')\")", - "generated?": false, - "precision": null, - "primary_key?": false, - "references": null, - "scale": null, - "size": null, - "source": "updated_at", - "type": "utc_datetime_usec" - } - ], - "base_filter": null, - "check_constraints": [], - "custom_indexes": [], - "custom_statements": [], - "has_create_action": true, - "hash": "FFDA74F44B5F11381D4C1F4DACA54901A1E02C3D181A88484AEED4E1ADA21B87", - "identities": [ - { - "all_tenants?": false, - "base_filter": null, - "index_name": "roles_unique_name_index", - "keys": [ - { - "type": "atom", - "value": "name" - } - ], - "name": "unique_name", - "nils_distinct?": true, - "where": null - } - ], - "multitenancy": { - "attribute": null, - "global": null, - "strategy": null - }, - "repo": "Elixir.Mv.Repo", - "schema": null, - "table": "roles" -} \ No newline at end of file diff --git a/priv/resource_snapshots/repo/users/20260106161215.json b/priv/resource_snapshots/repo/users/20260106161215.json index 886e1a1..3fcf712 100644 --- a/priv/resource_snapshots/repo/users/20260106161215.json +++ b/priv/resource_snapshots/repo/users/20260106161215.json @@ -99,7 +99,7 @@ "strategy": null }, "name": "users_role_id_fkey", - "on_delete": null, + "on_delete": "restrict", "on_update": null, "primary_key?": true, "schema": "public",