defmodule MvWeb.LiveHelpers do @moduledoc """ Shared LiveView lifecycle hooks and helper functions. ## on_mount Hooks - `:default` - Sets the user's locale from session (defaults to "de") - `:ensure_user_role_loaded` - Ensures current_user has role relationship loaded - `:check_page_permission_on_params` - Attaches handle_params hook to enforce page permission on client-side navigation (push_patch) ## Usage Add to LiveView modules via: ```elixir on_mount {MvWeb.LiveHelpers, :default} on_mount {MvWeb.LiveHelpers, :ensure_user_role_loaded} on_mount {MvWeb.LiveHelpers, :check_page_permission_on_params} ``` """ import Phoenix.Component alias MvWeb.Plugs.CheckPagePermission def on_mount(:default, _params, session, socket) do locale = session["locale"] || "de" Gettext.put_locale(locale) {:cont, socket} end def on_mount(:ensure_user_role_loaded, _params, _session, socket) do socket = ensure_user_role_loaded(socket) {:cont, socket} end def on_mount(:check_page_permission_on_params, _params, _session, socket) do {:cont, Phoenix.LiveView.attach_hook( socket, :check_page_permission, :handle_params, &check_page_permission_handle_params/3 )} end defp check_page_permission_handle_params(_params, uri, socket) do path = uri |> URI.parse() |> Map.get(:path, "/") || "/" if CheckPagePermission.public_path?(path) do {:cont, socket} else user = socket.assigns[:current_user] host = uri |> URI.parse() |> Map.get(:host) || "localhost" if CheckPagePermission.user_can_access_page?(user, path, router: MvWeb.Router, host: host) do {:cont, socket} else redirect_to = CheckPagePermission.redirect_target_for_user(user) socket = socket |> Phoenix.LiveView.put_flash(:error, "You don't have permission to access this page.") |> Phoenix.LiveView.push_navigate(to: redirect_to) {:halt, socket} end end end defp ensure_user_role_loaded(socket) do user = socket.assigns[:current_user] if user do # Use centralized Actor helper to ensure role is loaded user_with_role = Mv.Authorization.Actor.ensure_loaded(user) assign(socket, :current_user, user_with_role) else socket end end @doc """ Helper function to get the current actor (user) from socket assigns. Provides consistent access pattern across all LiveViews. Returns nil if no current_user is present. ## Examples actor = current_actor(socket) members = Membership.list_members!(actor: actor) """ @spec current_actor(Phoenix.LiveView.Socket.t()) :: Mv.Accounts.User.t() | nil def current_actor(socket) do socket.assigns[:current_user] || socket.assigns.current_user end @doc """ Converts an actor to Ash options list for authorization. Returns empty list if actor is nil. Delegates to `Mv.Helpers.ash_actor_opts/1` for consistency across the application. ## Examples opts = ash_actor_opts(actor) Ash.read(query, opts) """ @spec ash_actor_opts(Mv.Accounts.User.t() | nil) :: keyword() defdelegate ash_actor_opts(actor), to: Mv.Helpers @doc """ Submits an AshPhoenix form with consistent actor handling. This wrapper ensures that actor is always passed via `action_opts` in a consistent manner across all LiveViews. ## Examples case submit_form(form, params, actor) do {:ok, resource} -> # success {:error, form} -> # validation errors end """ @spec submit_form(AshPhoenix.Form.t(), map(), Mv.Accounts.User.t() | nil) :: {:ok, Ash.Resource.t()} | {:error, AshPhoenix.Form.t()} def submit_form(form, params, actor) do AshPhoenix.Form.submit(form, params: params, action_opts: ash_actor_opts(actor)) end end