Integrate Member policies in LiveViews

- Add on_mount hook to ensure user role is loaded in all Member LiveViews
- Pass actor parameter to all Ash operations (read, get, create, update, destroy, load)
This commit is contained in:
Moritz 2026-01-09 00:02:19 +01:00
parent dc3268cbf4
commit bc87893134
Signed by: moritz
GPG key ID: 1020A035E5DD0824
7 changed files with 167 additions and 74 deletions

View file

@ -21,6 +21,8 @@ defmodule MvWeb.MemberLive.Form do
"""
use MvWeb, :live_view
on_mount {MvWeb.LiveHelpers, :ensure_user_role_loaded}
alias Mv.MembershipFees
alias Mv.MembershipFees.MembershipFeeType
alias MvWeb.Helpers.MembershipFeeHelpers
@ -222,6 +224,8 @@ defmodule MvWeb.MemberLive.Form do
@impl true
def mount(params, _session, socket) do
# current_user should be set by on_mount hooks (LiveUserAuth + LiveHelpers)
actor = socket.assigns[:current_user] || socket.assigns.current_user
{:ok, custom_fields} = Mv.Membership.list_custom_fields()
initial_custom_field_values =
@ -239,14 +243,14 @@ defmodule MvWeb.MemberLive.Form do
member =
case params["id"] do
nil -> nil
id -> Ash.get!(Mv.Membership.Member, id, load: [:membership_fee_type])
id -> Ash.get!(Mv.Membership.Member, id, load: [:membership_fee_type], actor: actor)
end
page_title =
if is_nil(member), do: gettext("Create Member"), else: gettext("Edit Member")
# Load available membership fee types
available_fee_types = load_available_fee_types(member)
available_fee_types = load_available_fee_types(member, actor)
{:ok,
socket
@ -283,35 +287,59 @@ defmodule MvWeb.MemberLive.Form do
end
def handle_event("save", %{"member" => member_params}, socket) do
case AshPhoenix.Form.submit(socket.assigns.form, params: member_params) do
{:ok, member} ->
notify_parent({:saved, member})
try do
actor = socket.assigns[:current_user] || socket.assigns.current_user
action =
case socket.assigns.form.source.type do
:create -> gettext("create")
:update -> gettext("update")
other -> to_string(other)
end
case AshPhoenix.Form.submit(socket.assigns.form, params: member_params, actor: actor) do
{:ok, member} ->
handle_save_success(socket, member)
socket =
socket
|> put_flash(:info, gettext("Member %{action} successfully", action: action))
|> push_navigate(to: return_path(socket.assigns.return_to, member))
{:noreply, socket}
{:error, form} ->
{:noreply, assign(socket, form: form)}
{:error, form} ->
{:noreply, assign(socket, form: form)}
end
rescue
_e in [Ash.Error.Forbidden, Ash.Error.Forbidden.Policy] ->
handle_save_forbidden(socket)
end
end
defp handle_save_success(socket, member) do
notify_parent({:saved, member})
action = get_action_name(socket.assigns.form.source.type)
socket =
socket
|> put_flash(:info, gettext("Member %{action} successfully", action: action))
|> push_navigate(to: return_path(socket.assigns.return_to, member))
{:noreply, socket}
end
defp handle_save_forbidden(socket) do
# Handle policy violations that aren't properly displayed in forms
# AshPhoenix.Form doesn't implement FormData.Error protocol for Forbidden errors
action = get_action_name(socket.assigns.form.source.type)
error_message =
gettext("You do not have permission to %{action} members.", action: action)
{:noreply, put_flash(socket, :error, error_message)}
end
defp get_action_name(:create), do: gettext("create")
defp get_action_name(:update), do: gettext("update")
defp get_action_name(other), do: to_string(other)
defp notify_parent(msg), do: send(self(), {__MODULE__, msg})
defp assign_form(%{assigns: %{member: member}} = socket) do
defp assign_form(%{assigns: assigns} = socket) do
member = assigns.member
actor = assigns[:current_user] || assigns.current_user
form =
if member do
{:ok, member} = Ash.load(member, custom_field_values: [:custom_field])
{:ok, member} = Ash.load(member, custom_field_values: [:custom_field], actor: actor)
existing_custom_field_values =
member.custom_field_values
@ -342,7 +370,8 @@ defmodule MvWeb.MemberLive.Form do
api: Mv.Membership,
as: "member",
params: params,
forms: [auto?: true]
forms: [auto?: true],
actor: actor
)
missing_custom_field_values =
@ -360,7 +389,8 @@ defmodule MvWeb.MemberLive.Form do
api: Mv.Membership,
as: "member",
params: %{"custom_field_values" => socket.assigns[:initial_custom_field_values]},
forms: [auto?: true]
forms: [auto?: true],
actor: actor
)
end
@ -375,11 +405,11 @@ defmodule MvWeb.MemberLive.Form do
# Helper Functions
# -----------------------------------------------------------------
defp load_available_fee_types(member) do
defp load_available_fee_types(member, actor) do
all_types =
MembershipFeeType
|> Ash.Query.sort(name: :asc)
|> Ash.read!(domain: MembershipFees)
|> Ash.read!(domain: MembershipFees, actor: actor)
# If member has a fee type, filter to same interval
if member && member.membership_fee_type do