fix validation behaviour

This commit is contained in:
Moritz 2025-10-20 14:38:00 +02:00
parent 001fca1d16
commit 1495ef4592
Signed by: moritz
GPG key ID: 1020A035E5DD0824
3 changed files with 369 additions and 59 deletions

View file

@ -1,26 +1,46 @@
defmodule Mv.Accounts.User.Validations.EmailNotUsedByOtherMember do
@moduledoc """
Validates that the user's email is not already used by another member.
Allows syncing with linked member (excludes member_id from check).
Only validates when:
- User is already linked to a member (member_id != nil) AND email is changing
- User is being linked to a member (member relationship is changing)
This allows creating users with the same email as unlinked members.
"""
use Ash.Resource.Validation
@impl true
def validate(changeset, _opts, _context) do
case Ash.Changeset.fetch_change(changeset, :email) do
{:ok, new_email} ->
member_id = Ash.Changeset.get_attribute(changeset, :member_id)
check_email_uniqueness(new_email, member_id)
email_changing? = Ash.Changeset.changing_attribute?(changeset, :email)
member_changing? = Ash.Changeset.changing_relationship?(changeset, :member)
:error ->
:ok
member_id = Ash.Changeset.get_attribute(changeset, :member_id)
is_linked? = not is_nil(member_id)
# Only validate if:
# 1. User is linked AND email is changing
# 2. User is being linked/unlinked (member relationship changing)
should_validate? = (is_linked? and email_changing?) or member_changing?
if should_validate? do
case Ash.Changeset.fetch_change(changeset, :email) do
{:ok, new_email} ->
check_email_uniqueness(new_email, member_id)
:error ->
# No email change, get current email
current_email = Ash.Changeset.get_attribute(changeset, :email)
check_email_uniqueness(current_email, member_id)
end
else
:ok
end
end
defp check_email_uniqueness(new_email, exclude_member_id) do
defp check_email_uniqueness(email, exclude_member_id) do
query =
Mv.Membership.Member
|> Ash.Query.filter(email == ^to_string(new_email))
|> Ash.Query.filter(email == ^to_string(email))
|> maybe_exclude_id(exclude_member_id)
case Ash.read(query) do
@ -28,7 +48,7 @@ defmodule Mv.Accounts.User.Validations.EmailNotUsedByOtherMember do
:ok
{:ok, _} ->
{:error, field: :email, message: "is already used by another member", value: new_email}
{:error, field: :email, message: "is already used by another member", value: email}
{:error, _} ->
:ok

View file

@ -1,26 +1,37 @@
defmodule Mv.Membership.Member.Validations.EmailNotUsedByOtherUser do
@moduledoc """
Validates that the member's email is not already used by another user.
Allows syncing with linked user (excludes linked user from check).
Only validates when:
- Member is already linked to a user (user != nil) AND email is changing
- Member is being linked to a user (user relationship is changing)
This allows creating members with the same email as unlinked users.
"""
use Ash.Resource.Validation
@impl true
def validate(changeset, _opts, _context) do
case Ash.Changeset.fetch_change(changeset, :email) do
{:ok, new_email} ->
linked_user_id = get_linked_user_id(changeset.data)
check_email_uniqueness(new_email, linked_user_id)
email_changing? = Ash.Changeset.changing_attribute?(changeset, :email)
:error ->
:ok
linked_user_id = get_linked_user_id(changeset.data)
is_linked? = not is_nil(linked_user_id)
# Only validate if member is already linked AND email is changing
# Do NOT validate when member is being linked (email will be overridden from user)
should_validate? = is_linked? and email_changing?
if should_validate? do
new_email = Ash.Changeset.get_attribute(changeset, :email)
check_email_uniqueness(new_email, linked_user_id)
else
:ok
end
end
defp check_email_uniqueness(new_email, exclude_user_id) do
defp check_email_uniqueness(email, exclude_user_id) do
query =
Mv.Accounts.User
|> Ash.Query.filter(email == ^new_email)
|> Ash.Query.filter(email == ^email)
|> maybe_exclude_id(exclude_user_id)
case Ash.read(query) do
@ -28,7 +39,7 @@ defmodule Mv.Membership.Member.Validations.EmailNotUsedByOtherUser do
:ok
{:ok, _} ->
{:error, field: :email, message: "is already used by another user", value: new_email}
{:error, field: :email, message: "is already used by another user", value: email}
{:error, _} ->
:ok