diff --git a/lib/accounts/user.ex b/lib/accounts/user.ex index c65b882..541e29a 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,14 +77,10 @@ defmodule Mv.Accounts.User do # Manage the member relationship during user creation change manage_relationship(:member, :member, - # 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 + 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) ) end @@ -99,15 +95,11 @@ defmodule Mv.Accounts.User do # Manage the member relationship during user update change manage_relationship(:member, :member, - # 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 - ) + 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) + ) end # Admin action for direct password changes in admin panel @@ -165,7 +157,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) @@ -174,15 +166,13 @@ 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 5641528..1199023 100644 --- a/lib/membership/member.ex +++ b/lib/membership/member.ex @@ -39,14 +39,10 @@ defmodule Mv.Membership.Member do # Manage the user relationship during member creation change manage_relationship(:user, :user, - # 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 + 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) ) end @@ -80,14 +76,10 @@ defmodule Mv.Membership.Member do # Manage the user relationship during member update change manage_relationship(:user, :user, - # 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 + 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) ) end end @@ -99,7 +91,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 @@ -107,25 +99,18 @@ 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 - # 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: nil}} -> :ok # User is free to be linked + {:ok, %{member_id: ^current_member_id}} -> :ok # User already linked to this member (update scenario) {: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 b8992cf..304709c 100644 --- a/lib/mv_web/live/member_live/show.ex +++ b/lib/mv_web/live/member_live/show.ex @@ -37,16 +37,6 @@ 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 %> -