- Add Mv.Helpers module with ash_actor_opts/1 helper - Add current_actor/1 with @spec to LiveHelpers - Add ash_actor_opts/1 delegate and submit_form/3 wrapper to LiveHelpers - Standardize actor access pattern across LiveViews
111 lines
3.1 KiB
Elixir
111 lines
3.1 KiB
Elixir
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
|
|
|
|
## Usage
|
|
Add to LiveView modules via:
|
|
```elixir
|
|
on_mount {MvWeb.LiveHelpers, :default}
|
|
on_mount {MvWeb.LiveHelpers, :ensure_user_role_loaded}
|
|
```
|
|
"""
|
|
import Phoenix.Component
|
|
|
|
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
|
|
|
|
defp ensure_user_role_loaded(socket) do
|
|
if socket.assigns[:current_user] do
|
|
user = socket.assigns.current_user
|
|
user_with_role = load_user_role(user)
|
|
assign(socket, :current_user, user_with_role)
|
|
else
|
|
socket
|
|
end
|
|
end
|
|
|
|
defp load_user_role(user) do
|
|
case Map.get(user, :role) do
|
|
%Ash.NotLoaded{} -> load_role_safely(user)
|
|
nil -> load_role_safely(user)
|
|
_role -> user
|
|
end
|
|
end
|
|
|
|
defp load_role_safely(user) do
|
|
# Use self as actor for loading own role relationship
|
|
opts = [domain: Mv.Accounts, actor: user]
|
|
|
|
case Ash.load(user, :role, opts) do
|
|
{:ok, loaded_user} ->
|
|
loaded_user
|
|
|
|
{:error, error} ->
|
|
# Log warning if role loading fails - this can cause authorization issues
|
|
require Logger
|
|
Logger.warning("Failed to load role for user #{user.id}: #{inspect(error)}")
|
|
user
|
|
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
|