diff --git a/lib/accounts/user.ex b/lib/accounts/user.ex index 541e29a..c65b882 100644 --- a/lib/accounts/user.ex +++ b/lib/accounts/user.ex @@ -61,7 +61,7 @@ defmodule Mv.Accounts.User do actions do defaults [:read, :create, :destroy] - + update :update do primary? true require_atomic? false @@ -77,10 +77,14 @@ defmodule Mv.Accounts.User do # Manage the member relationship during user creation change manage_relationship(:member, :member, - on_lookup: :relate, # Look up existing member and relate to it - on_no_match: :error, # Error if member doesn't exist in database - on_match: :ignore, # If member already linked to this user, ignore (shouldn't happen in create) - on_missing: :ignore # If no member provided, that's fine (optional relationship) + # Look up existing member and relate to it + on_lookup: :relate, + # Error if member doesn't exist in database + on_no_match: :error, + # If member already linked to this user, ignore (shouldn't happen in create) + on_match: :ignore, + # If no member provided, that's fine (optional relationship) + on_missing: :ignore ) end @@ -95,11 +99,15 @@ defmodule Mv.Accounts.User do # Manage the member relationship during user update change manage_relationship(:member, :member, - on_lookup: :relate, # Look up existing member and relate to it - on_no_match: :error, # Error if member doesn't exist in database - on_match: :ignore, # If same member provided, that's fine (allows updates with same member) - on_missing: :unrelate # If no member provided, remove existing relationship (allows member removal) - ) + # Look up existing member and relate to it + on_lookup: :relate, + # Error if member doesn't exist in database + on_no_match: :error, + # If same member provided, that's fine (allows updates with same member) + on_match: :ignore, + # If no member provided, remove existing relationship (allows member removal) + on_missing: :unrelate + ) end # Admin action for direct password changes in admin panel @@ -157,7 +165,7 @@ defmodule Mv.Accounts.User do validate string_length(:password, min: 8) do where action_is([:register_with_password, :admin_set_password]) end - + # Prevent overwriting existing member relationship # This validation ensures race condition safety by requiring explicit two-step process: # 1. Remove existing member (set member to nil) @@ -166,13 +174,15 @@ defmodule Mv.Accounts.User do validate fn changeset, _context -> member_arg = Ash.Changeset.get_argument(changeset, :member) current_member_id = changeset.data.member_id - + # Only trigger if: # - member argument is provided AND has an ID # - user currently has a member # - the new member ID is different from current member ID - if member_arg && member_arg[:id] && current_member_id && member_arg[:id] != current_member_id do - {:error, field: :member, message: "User already has a member. Remove existing member first."} + if member_arg && member_arg[:id] && current_member_id && + member_arg[:id] != current_member_id do + {:error, + field: :member, message: "User already has a member. Remove existing member first."} else :ok end diff --git a/lib/membership/member.ex b/lib/membership/member.ex index 1199023..5641528 100644 --- a/lib/membership/member.ex +++ b/lib/membership/member.ex @@ -39,10 +39,14 @@ defmodule Mv.Membership.Member do # Manage the user relationship during member creation change manage_relationship(:user, :user, - on_lookup: :relate, # Look up existing user and relate to it - on_no_match: :error, # Error if user doesn't exist in database - on_match: :error, # Error if user is already linked to another member (prevents "stealing") - on_missing: :ignore # If no user provided, that's fine (optional relationship) + # Look up existing user and relate to it + on_lookup: :relate, + # Error if user doesn't exist in database + on_no_match: :error, + # Error if user is already linked to another member (prevents "stealing") + on_match: :error, + # If no user provided, that's fine (optional relationship) + on_missing: :ignore ) end @@ -76,10 +80,14 @@ defmodule Mv.Membership.Member do # Manage the user relationship during member update change manage_relationship(:user, :user, - on_lookup: :relate, # Look up existing user and relate to it - on_no_match: :error, # Error if user doesn't exist in database - on_match: :error, # Error if user is already linked to another member (prevents "stealing") - on_missing: :unrelate # If no user provided, remove existing relationship (allows user removal) + # Look up existing user and relate to it + on_lookup: :relate, + # Error if user doesn't exist in database + on_no_match: :error, + # Error if user is already linked to another member (prevents "stealing") + on_match: :error, + # If no user provided, remove existing relationship (allows user removal) + on_missing: :unrelate ) end end @@ -91,7 +99,7 @@ defmodule Mv.Membership.Member do validate present(:first_name) validate present(:last_name) validate present(:email) - + # Prevent linking to a user that already has a member # This validation prevents "stealing" users from other members by checking # if the target user is already linked to a different member @@ -99,18 +107,25 @@ defmodule Mv.Membership.Member do # if the user is already linked to THIS specific member, not ANY member validate fn changeset, _context -> user_arg = Ash.Changeset.get_argument(changeset, :user) - + if user_arg && user_arg[:id] do user_id = user_arg[:id] current_member_id = changeset.data.id - + # Check the current state of the user in the database case Ash.get(Mv.Accounts.User, user_id) do - {:ok, %{member_id: nil}} -> :ok # User is free to be linked - {:ok, %{member_id: ^current_member_id}} -> :ok # User already linked to this member (update scenario) + # User is free to be linked + {:ok, %{member_id: nil}} -> + :ok + + # User already linked to this member (update scenario) + {:ok, %{member_id: ^current_member_id}} -> + :ok + {:ok, %{member_id: _other_member_id}} -> # User is linked to a different member - prevent "stealing" {:error, field: :user, message: "User is already linked to another member"} + {:error, _} -> {:error, field: :user, message: "User not found"} end diff --git a/lib/mv_web/live/member_live/show.ex b/lib/mv_web/live/member_live/show.ex index 304709c..b8992cf 100644 --- a/lib/mv_web/live/member_live/show.ex +++ b/lib/mv_web/live/member_live/show.ex @@ -37,6 +37,16 @@ defmodule MvWeb.MemberLive.Show do <:item title={gettext("Street")}>{@member.street} <:item title={gettext("House Number")}>{@member.house_number} <:item title={gettext("Postal Code")}>{@member.postal_code} + <:item title={gettext("Linked User")}> + <%= if @member.user do %> + <.link navigate={~p"/users/#{@member.user}"} class="text-blue-600 hover:text-blue-800 underline"> + <.icon name="hero-user" class="h-4 w-4 inline mr-1" /> + {@member.user.email} + + <% else %> + {gettext("No user linked")} + <% end %> +