Member Resource Policies closes #345 #346
4 changed files with 27 additions and 6 deletions
|
|
@ -39,6 +39,7 @@ defmodule Mv.Membership.Member do
|
||||||
|
|
||||||
require Ash.Query
|
require Ash.Query
|
||||||
import Ash.Expr
|
import Ash.Expr
|
||||||
|
alias Mv.Helpers
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
alias Mv.Membership.Helpers.VisibilityConfig
|
alias Mv.Membership.Helpers.VisibilityConfig
|
||||||
|
|
@ -1217,7 +1218,7 @@ defmodule Mv.Membership.Member do
|
||||||
# Extracts custom field values from existing member data (update scenario)
|
# Extracts custom field values from existing member data (update scenario)
|
||||||
defp extract_existing_values(member_data, changeset) do
|
defp extract_existing_values(member_data, changeset) do
|
||||||
actor = Map.get(changeset.context, :actor)
|
actor = Map.get(changeset.context, :actor)
|
||||||
opts = if actor, do: [actor: actor], else: []
|
opts = Helpers.ash_actor_opts(actor)
|
||||||
|
|
||||||
case Ash.load(member_data, :custom_field_values, opts) do
|
case Ash.load(member_data, :custom_field_values, opts) do
|
||||||
{:ok, %{custom_field_values: existing_values}} ->
|
{:ok, %{custom_field_values: existing_values}} ->
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,17 @@ defmodule Mv.EmailSync.Loader do
|
||||||
@moduledoc """
|
@moduledoc """
|
||||||
Helper functions for loading linked records in email synchronization.
|
Helper functions for loading linked records in email synchronization.
|
||||||
Centralizes the logic for retrieving related User/Member entities.
|
Centralizes the logic for retrieving related User/Member entities.
|
||||||
|
|
||||||
|
## Authorization
|
||||||
|
|
||||||
|
This module runs systemically and accepts optional actor parameters.
|
||||||
|
When called from hooks/changes, actor is extracted from changeset context.
|
||||||
|
When called directly, actor should be provided for proper authorization.
|
||||||
|
|
||||||
|
All functions accept an optional `actor` parameter that is passed to Ash operations
|
||||||
|
to ensure proper authorization checks are performed.
|
||||||
"""
|
"""
|
||||||
|
alias Mv.Helpers
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Loads the member linked to a user, returns nil if not linked or on error.
|
Loads the member linked to a user, returns nil if not linked or on error.
|
||||||
|
|
@ -13,7 +23,7 @@ defmodule Mv.EmailSync.Loader do
|
||||||
def get_linked_member(%{member_id: nil}, _actor), do: nil
|
def get_linked_member(%{member_id: nil}, _actor), do: nil
|
||||||
|
|
||||||
def get_linked_member(%{member_id: id}, actor) do
|
def get_linked_member(%{member_id: id}, actor) do
|
||||||
opts = if actor, do: [actor: actor], else: []
|
opts = Helpers.ash_actor_opts(actor)
|
||||||
|
|
||||||
case Ash.get(Mv.Membership.Member, id, opts) do
|
case Ash.get(Mv.Membership.Member, id, opts) do
|
||||||
{:ok, member} -> member
|
{:ok, member} -> member
|
||||||
|
|
@ -27,7 +37,7 @@ defmodule Mv.EmailSync.Loader do
|
||||||
Accepts optional actor for authorization.
|
Accepts optional actor for authorization.
|
||||||
"""
|
"""
|
||||||
def get_linked_user(member, actor \\ nil) do
|
def get_linked_user(member, actor \\ nil) do
|
||||||
opts = if actor, do: [actor: actor], else: []
|
opts = Helpers.ash_actor_opts(actor)
|
||||||
|
|
||||||
case Ash.load(member, :user, opts) do
|
case Ash.load(member, :user, opts) do
|
||||||
{:ok, %{user: user}} -> user
|
{:ok, %{user: user}} -> user
|
||||||
|
|
@ -42,7 +52,7 @@ defmodule Mv.EmailSync.Loader do
|
||||||
Accepts optional actor for authorization.
|
Accepts optional actor for authorization.
|
||||||
"""
|
"""
|
||||||
def load_linked_user!(member, actor \\ nil) do
|
def load_linked_user!(member, actor \\ nil) do
|
||||||
opts = if actor, do: [actor: actor], else: []
|
opts = Helpers.ash_actor_opts(actor)
|
||||||
|
|
||||||
case Ash.load(member, :user, opts) do
|
case Ash.load(member, :user, opts) do
|
||||||
{:ok, %{user: user}} when not is_nil(user) -> {:ok, user}
|
{:ok, %{user: user}} when not is_nil(user) -> {:ok, user}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ defmodule Mv.Membership.Member.Validations.EmailNotUsedByOtherUser do
|
||||||
This allows creating members with the same email as unlinked users.
|
This allows creating members with the same email as unlinked users.
|
||||||
"""
|
"""
|
||||||
use Ash.Resource.Validation
|
use Ash.Resource.Validation
|
||||||
|
alias Mv.Helpers
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Validates email uniqueness across linked Member-User pairs.
|
Validates email uniqueness across linked Member-User pairs.
|
||||||
|
|
@ -51,7 +52,7 @@ defmodule Mv.Membership.Member.Validations.EmailNotUsedByOtherUser do
|
||||||
|> Ash.Query.filter(email == ^email)
|
|> Ash.Query.filter(email == ^email)
|
||||||
|> maybe_exclude_id(exclude_user_id)
|
|> maybe_exclude_id(exclude_user_id)
|
||||||
|
|
||||||
opts = if actor, do: [actor: actor], else: []
|
opts = Helpers.ash_actor_opts(actor)
|
||||||
|
|
||||||
case Ash.read(query, opts) do
|
case Ash.read(query, opts) do
|
||||||
{:ok, []} ->
|
{:ok, []} ->
|
||||||
|
|
@ -69,7 +70,7 @@ defmodule Mv.Membership.Member.Validations.EmailNotUsedByOtherUser do
|
||||||
defp maybe_exclude_id(query, id), do: Ash.Query.filter(query, id != ^id)
|
defp maybe_exclude_id(query, id), do: Ash.Query.filter(query, id != ^id)
|
||||||
|
|
||||||
defp get_linked_user_id(member_data, actor) do
|
defp get_linked_user_id(member_data, actor) do
|
||||||
opts = if actor, do: [actor: actor], else: []
|
opts = Helpers.ash_actor_opts(actor)
|
||||||
|
|
||||||
case Ash.load(member_data, :user, opts) do
|
case Ash.load(member_data, :user, opts) do
|
||||||
{:ok, %{user: %{id: id}}} -> id
|
{:ok, %{user: %{id: id}}} -> id
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,15 @@ defmodule Mv.MembershipFees.CycleGenerator do
|
||||||
Uses PostgreSQL advisory locks to prevent race conditions when generating
|
Uses PostgreSQL advisory locks to prevent race conditions when generating
|
||||||
cycles for the same member concurrently.
|
cycles for the same member concurrently.
|
||||||
|
|
||||||
|
## Authorization
|
||||||
|
|
||||||
|
This module runs systemically and accepts optional actor parameters.
|
||||||
|
When called from hooks/changes, actor is extracted from changeset context.
|
||||||
|
When called directly, actor should be provided for proper authorization.
|
||||||
|
|
||||||
|
All functions accept an optional `actor` parameter in the `opts` keyword list
|
||||||
|
that is passed to Ash operations to ensure proper authorization checks are performed.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
# Generate cycles for a single member
|
# Generate cycles for a single member
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue