Fix error handling and actor access in MemberLive.Index

Replace bang calls with proper error handling and use current_actor/1
helper for consistent actor access.
This commit is contained in:
Moritz 2026-01-09 05:26:11 +01:00
parent 145a76348c
commit 351eac4c02
Signed by: moritz
GPG key ID: 1020A035E5DD0824

View file

@ -31,6 +31,7 @@ defmodule MvWeb.MemberLive.Index do
require Ash.Query require Ash.Query
import Ash.Expr import Ash.Expr
import MvWeb.LiveHelpers, only: [current_actor: 1]
alias Mv.Membership alias Mv.Membership
alias MvWeb.Helpers.DateFormatter alias MvWeb.Helpers.DateFormatter
@ -60,7 +61,7 @@ defmodule MvWeb.MemberLive.Index do
# Note: Using Ash.read! (bang version) - errors will be handled by Phoenix LiveView # Note: Using Ash.read! (bang version) - errors will be handled by Phoenix LiveView
# and result in a 500 error page. This is appropriate for LiveViews where errors # and result in a 500 error page. This is appropriate for LiveViews where errors
# should be visible to the user rather than silently failing. # should be visible to the user rather than silently failing.
actor = socket.assigns[:current_user] actor = current_actor(socket)
custom_fields_visible = custom_fields_visible =
Mv.Membership.CustomField Mv.Membership.CustomField
@ -134,14 +135,41 @@ defmodule MvWeb.MemberLive.Index do
""" """
@impl true @impl true
def handle_event("delete", %{"id" => id}, socket) do def handle_event("delete", %{"id" => id}, socket) do
# Note: Using bang versions (!) - errors will be handled by Phoenix LiveView actor = current_actor(socket)
# This ensures users see error messages if deletion fails (e.g., permission denied)
actor = socket.assigns[:current_user]
member = Ash.get!(Mv.Membership.Member, id, actor: actor)
Ash.destroy!(member, actor: actor)
updated_members = Enum.reject(socket.assigns.members, &(&1.id == id)) case Ash.get(Mv.Membership.Member, id, actor: actor) do
{:noreply, assign(socket, :members, updated_members)} {:ok, member} ->
case Ash.destroy(member, actor: actor) do
:ok ->
updated_members = Enum.reject(socket.assigns.members, &(&1.id == id))
{:noreply,
socket
|> assign(:members, updated_members)
|> put_flash(:info, gettext("Member deleted successfully"))}
{:error, %Ash.Error.Forbidden{}} ->
{:noreply,
put_flash(
socket,
:error,
gettext("You do not have permission to delete this member")
)}
{:error, error} ->
{:noreply, put_flash(socket, :error, format_error(error))}
end
{:error, %Ash.Error.Query.NotFound{}} ->
{:noreply, put_flash(socket, :error, gettext("Member not found"))}
{:error, %Ash.Error.Forbidden{} = _error} ->
{:noreply,
put_flash(socket, :error, gettext("You do not have permission to access this member"))}
{:error, error} ->
{:noreply, put_flash(socket, :error, format_error(error))}
end
end end
@impl true @impl true
@ -241,6 +269,24 @@ defmodule MvWeb.MemberLive.Index do
end end
end end
# Helper to format errors for display
defp format_error(%Ash.Error.Invalid{errors: errors}) do
error_messages =
Enum.map(errors, fn error ->
case error do
%{field: field, message: message} -> "#{field}: #{message}"
%{message: message} -> message
_ -> inspect(error)
end
end)
Enum.join(error_messages, ", ")
end
defp format_error(error) do
inspect(error)
end
# ----------------------------------------------------------------- # -----------------------------------------------------------------
# Handle Infos from Child Components # Handle Infos from Child Components
# ----------------------------------------------------------------- # -----------------------------------------------------------------
@ -683,7 +729,7 @@ defmodule MvWeb.MemberLive.Index do
# Note: Using Ash.read! - errors will be handled by Phoenix LiveView # Note: Using Ash.read! - errors will be handled by Phoenix LiveView
# This is appropriate for data loading in LiveViews # This is appropriate for data loading in LiveViews
actor = socket.assigns[:current_user] actor = current_actor(socket)
members = Ash.read!(query, actor: actor) members = Ash.read!(query, actor: actor)
# Custom field values are already filtered at the database level in load_custom_field_values/2 # Custom field values are already filtered at the database level in load_custom_field_values/2