diff --git a/lib/mv_web/live/custom_field_value_live/index.ex b/lib/mv_web/live/custom_field_value_live/index.ex index b52fd96..0847cd6 100644 --- a/lib/mv_web/live/custom_field_value_live/index.ex +++ b/lib/mv_web/live/custom_field_value_live/index.ex @@ -23,6 +23,9 @@ defmodule MvWeb.CustomFieldValueLive.Index do """ use MvWeb, :live_view + on_mount {MvWeb.LiveHelpers, :ensure_user_role_loaded} + import MvWeb.LiveHelpers, only: [current_actor: 1] + @impl true def render(assigns) do ~H""" @@ -70,17 +73,60 @@ defmodule MvWeb.CustomFieldValueLive.Index do @impl true def mount(_params, _session, socket) do + actor = current_actor(socket) + {:ok, socket |> assign(:page_title, "Listing Custom field values") - |> stream(:custom_field_values, Ash.read!(Mv.Membership.CustomFieldValue))} + |> stream(:custom_field_values, Ash.read!(Mv.Membership.CustomFieldValue, actor: actor))} end @impl true def handle_event("delete", %{"id" => id}, socket) do - custom_field_value = Ash.get!(Mv.Membership.CustomFieldValue, id) - Ash.destroy!(custom_field_value) + actor = MvWeb.LiveHelpers.current_actor(socket) - {:noreply, stream_delete(socket, :custom_field_values, custom_field_value)} + case Ash.get(Mv.Membership.CustomFieldValue, id, actor: actor) do + {:ok, custom_field_value} -> + case Ash.destroy(custom_field_value, actor: actor) do + :ok -> + {:noreply, + socket + |> stream_delete(:custom_field_values, custom_field_value) + |> put_flash(:info, gettext("Custom field value deleted successfully"))} + + {:error, %Ash.Error.Forbidden{}} -> + {:noreply, + put_flash( + socket, + :error, + gettext("You do not have permission to delete this custom field value") + )} + + {:error, error} -> + {:noreply, put_flash(socket, :error, format_error(error))} + end + + {:error, %Ash.Error.Query.NotFound{}} -> + {:noreply, put_flash(socket, :error, gettext("Custom field value not found"))} + + {:error, %Ash.Error.Forbidden{} = _error} -> + {:noreply, + put_flash( + socket, + :error, + gettext("You do not have permission to access this custom field value") + )} + + {:error, error} -> + {:noreply, put_flash(socket, :error, format_error(error))} + end + end + + defp format_error(%Ash.Error.Invalid{errors: errors}) do + Enum.map_join(errors, ", ", fn %{message: message} -> message end) + end + + defp format_error(error) do + inspect(error) end end diff --git a/lib/mv_web/live/member_live/index.ex b/lib/mv_web/live/member_live/index.ex index f7eddfb..a972d1e 100644 --- a/lib/mv_web/live/member_live/index.ex +++ b/lib/mv_web/live/member_live/index.ex @@ -58,9 +58,8 @@ defmodule MvWeb.MemberLive.Index do @impl true def mount(_params, session, socket) do # Load custom fields that should be shown in overview (for display) - # 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 - # should be visible to the user rather than silently failing. + # Errors in mount are handled by Phoenix LiveView and result in a 500 error page. + # This is appropriate for initialization errors that should be visible to the user. actor = current_actor(socket) custom_fields_visible = @@ -727,8 +726,7 @@ defmodule MvWeb.MemberLive.Index do socket.assigns.custom_fields_visible ) - # Note: Using Ash.read! - errors will be handled by Phoenix LiveView - # This is appropriate for data loading in LiveViews + # Errors in handle_params are handled by Phoenix LiveView actor = current_actor(socket) members = Ash.read!(query, actor: actor) diff --git a/lib/mv_web/live/membership_fee_type_live/index.ex b/lib/mv_web/live/membership_fee_type_live/index.ex index 976e9c1..fb2b9e7 100644 --- a/lib/mv_web/live/membership_fee_type_live/index.ex +++ b/lib/mv_web/live/membership_fee_type_live/index.ex @@ -133,18 +133,43 @@ defmodule MvWeb.MembershipFeeTypeLive.Index do @impl true def handle_event("delete", %{"id" => id}, socket) do - fee_type = Ash.get!(MembershipFeeType, id, domain: MembershipFees) + actor = current_actor(socket) - case Ash.destroy(fee_type, domain: MembershipFees) do - :ok -> - updated_types = Enum.reject(socket.assigns.membership_fee_types, &(&1.id == id)) - updated_counts = Map.delete(socket.assigns.member_counts, id) + case Ash.get(MembershipFeeType, id, domain: MembershipFees, actor: actor) do + {:ok, fee_type} -> + case Ash.destroy(fee_type, domain: MembershipFees, actor: actor) do + :ok -> + updated_types = Enum.reject(socket.assigns.membership_fee_types, &(&1.id == id)) + updated_counts = Map.delete(socket.assigns.member_counts, id) + {:noreply, + socket + |> assign(:membership_fee_types, updated_types) + |> assign(:member_counts, updated_counts) + |> put_flash(:info, gettext("Membership fee type deleted"))} + + {:error, %Ash.Error.Forbidden{}} -> + {:noreply, + put_flash( + socket, + :error, + gettext("You do not have permission to delete this membership fee type") + )} + + {:error, error} -> + {:noreply, put_flash(socket, :error, format_error(error))} + end + + {:error, %Ash.Error.Query.NotFound{}} -> + {:noreply, put_flash(socket, :error, gettext("Membership fee type not found"))} + + {:error, %Ash.Error.Forbidden{} = _error} -> {:noreply, - socket - |> assign(:membership_fee_types, updated_types) - |> assign(:member_counts, updated_counts) - |> put_flash(:info, gettext("Membership fee type deleted"))} + put_flash( + socket, + :error, + gettext("You do not have permission to access this membership fee type") + )} {:error, error} -> {:noreply, put_flash(socket, :error, format_error(error))} diff --git a/lib/mv_web/live/role_live/index.ex b/lib/mv_web/live/role_live/index.ex index 9d75da6..4f8e45d 100644 --- a/lib/mv_web/live/role_live/index.ex +++ b/lib/mv_web/live/role_live/index.ex @@ -118,7 +118,7 @@ defmodule MvWeb.RoleLive.Index do @spec load_roles(map() | nil) :: [Mv.Authorization.Role.t()] defp load_roles(actor) do - opts = if actor, do: [actor: actor], else: [] + opts = MvWeb.LiveHelpers.ash_actor_opts(actor) case Authorization.list_roles(opts) do {:ok, roles} -> Enum.sort_by(roles, & &1.name) diff --git a/lib/mv_web/live/user_live/index.ex b/lib/mv_web/live/user_live/index.ex index bd9c881..5e5949e 100644 --- a/lib/mv_web/live/user_live/index.ex +++ b/lib/mv_web/live/user_live/index.ex @@ -43,11 +43,41 @@ defmodule MvWeb.UserLive.Index do @impl true def handle_event("delete", %{"id" => id}, socket) do - user = Ash.get!(Mv.Accounts.User, id, domain: Mv.Accounts) - Ash.destroy!(user, domain: Mv.Accounts) + actor = current_actor(socket) - updated_users = Enum.reject(socket.assigns.users, &(&1.id == id)) - {:noreply, assign(socket, :users, updated_users)} + case Ash.get(Mv.Accounts.User, id, domain: Mv.Accounts, actor: actor) do + {:ok, user} -> + case Ash.destroy(user, domain: Mv.Accounts, actor: actor) do + :ok -> + updated_users = Enum.reject(socket.assigns.users, &(&1.id == id)) + + {:noreply, + socket + |> assign(:users, updated_users) + |> put_flash(:info, gettext("User deleted successfully"))} + + {:error, %Ash.Error.Forbidden{}} -> + {:noreply, + put_flash( + socket, + :error, + gettext("You do not have permission to delete this user") + )} + + {:error, error} -> + {:noreply, put_flash(socket, :error, format_error(error))} + end + + {:error, %Ash.Error.Query.NotFound{}} -> + {:noreply, put_flash(socket, :error, gettext("User not found"))} + + {:error, %Ash.Error.Forbidden{} = _error} -> + {:noreply, + put_flash(socket, :error, gettext("You do not have permission to access this user"))} + + {:error, error} -> + {:noreply, put_flash(socket, :error, format_error(error))} + end end # Selects one user in the list of users @@ -108,4 +138,12 @@ defmodule MvWeb.UserLive.Index do defp toggle_order(:desc), do: :asc defp sort_fun(:asc), do: &<=/2 defp sort_fun(:desc), do: &>=/2 + + defp format_error(%Ash.Error.Invalid{errors: errors}) do + Enum.map_join(errors, ", ", fn %{message: message} -> message end) + end + + defp format_error(error) do + inspect(error) + end end