From cf220b9730688fa4ec24eeb286e54040dbc4cdd5 Mon Sep 17 00:00:00 2001 From: Moritz Date: Thu, 8 Jan 2026 14:25:28 +0100 Subject: [PATCH] refactor: extract shared helpers for RoleLive modules Extract format_error and permission_set_badge_class functions into MvWeb.RoleLive.Helpers module to eliminate code duplication between Index and Show LiveViews. --- lib/mv_web/live/role_live/form.ex | 10 +++------- lib/mv_web/live/role_live/helpers.ex | 27 +++++++++++++++++++++++++++ lib/mv_web/live/role_live/index.ex | 18 ++++++------------ lib/mv_web/live/role_live/show.ex | 23 ++++++++--------------- 4 files changed, 44 insertions(+), 34 deletions(-) create mode 100644 lib/mv_web/live/role_live/helpers.ex diff --git a/lib/mv_web/live/role_live/form.ex b/lib/mv_web/live/role_live/form.ex index abb5f59..6388ec1 100644 --- a/lib/mv_web/live/role_live/form.ex +++ b/lib/mv_web/live/role_live/form.ex @@ -15,6 +15,8 @@ defmodule MvWeb.RoleLive.Form do alias Mv.Authorization.PermissionSets + import MvWeb.RoleLive.Helpers + on_mount {MvWeb.LiveHelpers, :ensure_user_role_loaded} @impl true @@ -134,6 +136,7 @@ defmodule MvWeb.RoleLive.Form do end rescue e in [Ash.Error.Invalid] -> + # Handle exceptions that Ash.get might throw (e.g., policy violations) case e do %Ash.Error.Invalid{errors: [%Ash.Error.Query.NotFound{} | _]} -> {:ok, @@ -231,11 +234,4 @@ defmodule MvWeb.RoleLive.Form do defp return_path("show", _role), do: ~p"/admin/roles" defp return_path(_, role) when not is_nil(role), do: ~p"/admin/roles/#{role.id}" defp return_path(_, _role), do: ~p"/admin/roles" - - defp format_error(%Ash.Error.Invalid{} = error) do - Enum.map_join(error.errors, ", ", fn e -> e.message end) - end - - defp format_error(error) when is_binary(error), do: error - defp format_error(_error), do: gettext("An error occurred") end diff --git a/lib/mv_web/live/role_live/helpers.ex b/lib/mv_web/live/role_live/helpers.ex new file mode 100644 index 0000000..9d4e77d --- /dev/null +++ b/lib/mv_web/live/role_live/helpers.ex @@ -0,0 +1,27 @@ +defmodule MvWeb.RoleLive.Helpers do + @moduledoc """ + Shared helper functions for RoleLive modules. + """ + use Gettext, backend: MvWeb.Gettext + + @doc """ + Formats an error for display to the user. + """ + @spec format_error(Ash.Error.Invalid.t() | String.t() | any()) :: String.t() + def format_error(%Ash.Error.Invalid{} = error) do + Enum.map_join(error.errors, ", ", fn e -> e.message end) + end + + def format_error(error) when is_binary(error), do: error + def format_error(_error), do: gettext("An error occurred") + + @doc """ + Returns the CSS badge class for a permission set name. + """ + @spec permission_set_badge_class(String.t()) :: String.t() + def permission_set_badge_class("own_data"), do: "badge badge-neutral badge-sm" + def permission_set_badge_class("read_only"), do: "badge badge-info badge-sm" + def permission_set_badge_class("normal_user"), do: "badge badge-success badge-sm" + def permission_set_badge_class("admin"), do: "badge badge-error badge-sm" + def permission_set_badge_class(_), do: "badge badge-ghost badge-sm" +end diff --git a/lib/mv_web/live/role_live/index.ex b/lib/mv_web/live/role_live/index.ex index 1ba59be..0099929 100644 --- a/lib/mv_web/live/role_live/index.ex +++ b/lib/mv_web/live/role_live/index.ex @@ -21,6 +21,8 @@ defmodule MvWeb.RoleLive.Index do require Ash.Query + import MvWeb.RoleLive.Helpers + on_mount {MvWeb.LiveHelpers, :ensure_user_role_loaded} @impl true @@ -113,6 +115,7 @@ defmodule MvWeb.RoleLive.Index do end end + @spec load_roles(map() | nil) :: [Mv.Authorization.Role.t()] defp load_roles(actor) do opts = if actor, do: [actor: actor], else: [] @@ -123,6 +126,7 @@ defmodule MvWeb.RoleLive.Index do end # Loads all user counts for roles in a single query to avoid N+1 queries + @spec load_user_counts([Mv.Authorization.Role.t()], map() | nil) :: %{Ecto.UUID.t() => non_neg_integer()} defp load_user_counts(roles, actor) do role_ids = Enum.map(roles, & &1.id) @@ -149,11 +153,13 @@ defmodule MvWeb.RoleLive.Index do end # Gets user count from preloaded assigns map + @spec get_user_count(Mv.Authorization.Role.t(), %{Ecto.UUID.t() => non_neg_integer()}) :: non_neg_integer() defp get_user_count(role, user_counts) do Map.get(user_counts, role.id, 0) end # Recalculates user count for a specific role (used before deletion) + @spec recalculate_user_count(Mv.Authorization.Role.t(), map() | nil) :: non_neg_integer() defp recalculate_user_count(role, actor) do opts = [domain: Mv.Accounts] opts = if actor, do: Keyword.put(opts, :actor, actor), else: opts @@ -164,16 +170,4 @@ defmodule MvWeb.RoleLive.Index do end end - defp format_error(%Ash.Error.Invalid{} = error) do - Enum.map_join(error.errors, ", ", fn e -> e.message end) - end - - defp format_error(error) when is_binary(error), do: error - defp format_error(_error), do: gettext("An error occurred") - - defp permission_set_badge_class("own_data"), do: "badge badge-neutral badge-sm" - defp permission_set_badge_class("read_only"), do: "badge badge-info badge-sm" - defp permission_set_badge_class("normal_user"), do: "badge badge-success badge-sm" - defp permission_set_badge_class("admin"), do: "badge badge-error badge-sm" - defp permission_set_badge_class(_), do: "badge badge-ghost badge-sm" end diff --git a/lib/mv_web/live/role_live/show.ex b/lib/mv_web/live/role_live/show.ex index 292c41c..8400728 100644 --- a/lib/mv_web/live/role_live/show.ex +++ b/lib/mv_web/live/role_live/show.ex @@ -16,6 +16,8 @@ defmodule MvWeb.RoleLive.Show do require Ash.Query + import MvWeb.RoleLive.Helpers + on_mount {MvWeb.LiveHelpers, :ensure_user_role_loaded} @impl true @@ -27,10 +29,10 @@ defmodule MvWeb.RoleLive.Show do domain: Mv.Authorization, actor: socket.assigns[:current_user] ) do - {:ok, role} -> - user_count = load_user_count(role, socket.assigns[:current_user]) + {:ok, role} -> + user_count = load_user_count(role, socket.assigns[:current_user]) - {:ok, + {:ok, socket |> assign(:page_title, gettext("Show Role")) |> assign(:role, role) @@ -50,6 +52,7 @@ defmodule MvWeb.RoleLive.Show do end rescue e in [Ash.Error.Invalid] -> + # Handle exceptions that Ash.get might throw (e.g., policy violations) case e do %Ash.Error.Invalid{errors: [%Ash.Error.Query.NotFound{} | _]} -> {:ok, @@ -138,6 +141,7 @@ defmodule MvWeb.RoleLive.Show do end # Recalculates user count for a specific role (used before deletion) + @spec recalculate_user_count(Mv.Authorization.Role.t(), map() | nil) :: non_neg_integer() defp recalculate_user_count(role, actor) do opts = [domain: Mv.Accounts] opts = if actor, do: Keyword.put(opts, :actor, actor), else: opts @@ -149,6 +153,7 @@ defmodule MvWeb.RoleLive.Show do end # Loads user count for initial display (uses same logic as recalculate) + @spec load_user_count(Mv.Authorization.Role.t(), map() | nil) :: non_neg_integer() defp load_user_count(role, actor) do recalculate_user_count(role, actor) end @@ -209,16 +214,4 @@ defmodule MvWeb.RoleLive.Show do """ end - defp format_error(%Ash.Error.Invalid{} = error) do - Enum.map_join(error.errors, ", ", fn e -> e.message end) - end - - defp format_error(error) when is_binary(error), do: error - defp format_error(_error), do: gettext("An error occurred") - - defp permission_set_badge_class("own_data"), do: "badge badge-neutral badge-sm" - defp permission_set_badge_class("read_only"), do: "badge badge-info badge-sm" - defp permission_set_badge_class("normal_user"), do: "badge badge-success badge-sm" - defp permission_set_badge_class("admin"), do: "badge badge-error badge-sm" - defp permission_set_badge_class(_), do: "badge badge-ghost badge-sm" end