From 8ad5201e1a004f22ed77bf9c2f3ca2e9575be653 Mon Sep 17 00:00:00 2001 From: Moritz Date: Tue, 27 Jan 2026 14:29:13 +0100 Subject: [PATCH] Hide system actor from user list and block show/edit Index: filter out SystemActor.system_user_email() in query. Show/Form: redirect to /users with flash when viewing or editing system actor user. Index format_error: handle Ash errors without :message field. --- lib/mv_web/live/user_live/form.ex | 25 +++++++++++++++++++++++-- lib/mv_web/live/user_live/index.ex | 16 +++++++++++++--- lib/mv_web/live/user_live/show.ex | 15 +++++++++++---- 3 files changed, 47 insertions(+), 9 deletions(-) diff --git a/lib/mv_web/live/user_live/form.ex b/lib/mv_web/live/user_live/form.ex index 6cf3f0f..28af7c4 100644 --- a/lib/mv_web/live/user_live/form.ex +++ b/lib/mv_web/live/user_live/form.ex @@ -266,10 +266,31 @@ defmodule MvWeb.UserLive.Form do user = case params["id"] do - nil -> nil - id -> Ash.get!(Mv.Accounts.User, id, domain: Mv.Accounts, load: [:member], actor: actor) + nil -> + nil + + id -> + loaded = + Ash.get!(Mv.Accounts.User, id, domain: Mv.Accounts, load: [:member], actor: actor) + + if to_string(loaded.email) == Mv.Helpers.SystemActor.system_user_email() do + {:redirect, loaded} + else + loaded + end end + if match?({:redirect, _}, user) do + {:ok, + socket + |> put_flash(:error, gettext("This user cannot be edited.")) + |> push_navigate(to: ~p"/users")} + else + mount_continue(user, params, socket) + end + end + + defp mount_continue(user, params, socket) do action = if is_nil(user), do: gettext("New"), else: gettext("Edit") page_title = action <> " " <> gettext("User") diff --git a/lib/mv_web/live/user_live/index.ex b/lib/mv_web/live/user_live/index.ex index 2062ae6..ef3583e 100644 --- a/lib/mv_web/live/user_live/index.ex +++ b/lib/mv_web/live/user_live/index.ex @@ -25,10 +25,17 @@ defmodule MvWeb.UserLive.Index do import MvWeb.LiveHelpers, only: [current_actor: 1] + require Ash.Query + @impl true def mount(_params, _session, socket) do actor = current_actor(socket) - users = Ash.read!(Mv.Accounts.User, domain: Mv.Accounts, load: [:member], actor: actor) + + users = + Mv.Accounts.User + |> Ash.Query.filter(email != ^Mv.Helpers.SystemActor.system_user_email()) + |> Ash.read!(domain: Mv.Accounts, load: [:member], actor: actor) + sorted = Enum.sort_by(users, & &1.email) {:ok, @@ -138,8 +145,11 @@ defmodule MvWeb.UserLive.Index do 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) + defp format_error(%Ash.Error.Invalid{errors: errors}) when is_list(errors) do + Enum.map_join(errors, ", ", fn + %{message: message} when is_binary(message) -> message + other -> inspect(other) + end) end defp format_error(error) do diff --git a/lib/mv_web/live/user_live/show.ex b/lib/mv_web/live/user_live/show.ex index 641e091..fe2dd24 100644 --- a/lib/mv_web/live/user_live/show.ex +++ b/lib/mv_web/live/user_live/show.ex @@ -75,9 +75,16 @@ defmodule MvWeb.UserLive.Show do actor = current_actor(socket) user = Ash.get!(Mv.Accounts.User, id, domain: Mv.Accounts, load: [:member], actor: actor) - {:ok, - socket - |> assign(:page_title, gettext("Show User")) - |> assign(:user, user)} + if to_string(user.email) == Mv.Helpers.SystemActor.system_user_email() do + {:ok, + socket + |> put_flash(:error, gettext("This user cannot be viewed.")) + |> push_navigate(to: ~p"/users")} + else + {:ok, + socket + |> assign(:page_title, gettext("Show User")) + |> assign(:user, user)} + end end end