76 lines
2.2 KiB
Elixir
76 lines
2.2 KiB
Elixir
defmodule MvWeb.RoleLive.Index do
|
|
@moduledoc """
|
|
LiveView for displaying and managing the role list.
|
|
|
|
## Features
|
|
- List all roles with name, description, permission_set_name, is_system_role
|
|
- Create new roles
|
|
- Navigate to role details (row click) and edit from details header
|
|
- Delete only via Danger zone on role show page
|
|
|
|
## Security
|
|
Only admins can access this page (enforced by authorization).
|
|
"""
|
|
use MvWeb, :live_view
|
|
|
|
alias Mv.Accounts
|
|
alias Mv.Authorization
|
|
|
|
require Ash.Query
|
|
|
|
import MvWeb.RoleLive.Helpers, only: [permission_set_badge_class: 1]
|
|
|
|
@impl true
|
|
def mount(_params, _session, socket) do
|
|
actor = socket.assigns[:current_user]
|
|
roles = load_roles(actor)
|
|
user_counts = load_user_counts(roles, actor)
|
|
|
|
{:ok,
|
|
socket
|
|
|> assign(:page_title, gettext("Listing Roles"))
|
|
|> assign(:roles, roles)
|
|
|> assign(:user_counts, user_counts)}
|
|
end
|
|
|
|
@spec load_roles(map() | nil) :: [Mv.Authorization.Role.t()]
|
|
defp load_roles(actor) do
|
|
opts = MvWeb.LiveHelpers.ash_actor_opts(actor)
|
|
|
|
case Authorization.list_roles(opts) do
|
|
{:ok, roles} -> Enum.sort_by(roles, & &1.name)
|
|
{:error, _} -> []
|
|
end
|
|
end
|
|
|
|
# Loads all user counts for roles using DB-side aggregation for better performance
|
|
@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)
|
|
|
|
# Use Ecto directly for efficient GROUP BY COUNT query
|
|
# This is much more performant than loading all users and counting in Elixir
|
|
# Note: We bypass Ash here for performance, but this is a simple read-only query
|
|
import Ecto.Query
|
|
|
|
query =
|
|
from u in Accounts.User,
|
|
where: u.role_id in ^role_ids,
|
|
group_by: u.role_id,
|
|
select: {u.role_id, count(u.id)}
|
|
|
|
results = Mv.Repo.all(query)
|
|
|
|
results
|
|
|> Enum.into(%{}, fn {role_id, count} -> {role_id, count} end)
|
|
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
|
|
end
|