refactor: email sync changes

This commit is contained in:
Moritz 2025-10-17 14:33:25 +02:00
parent b97c3a121b
commit c7a56b201a
Signed by: moritz
GPG key ID: 1020A035E5DD0824
5 changed files with 102 additions and 138 deletions

View file

@ -1,47 +1,30 @@
defmodule Mv.Accounts.User.Changes.OverrideMemberEmailOnLink do
@moduledoc """
Overrides member email with user email when linking a user to a member.
When a user is linked to a member (either during creation or update),
this change ensures that the member's email is updated to match the user's email.
Overrides member email with user email when linking.
User.email is the source of truth when a link is established.
Uses `around_transaction` to guarantee atomicity - both the user
creation/update and member email override happen in the SAME database transaction.
"""
use Ash.Resource.Change
alias Mv.EmailSync.Helpers
alias Mv.EmailSync.{Helpers, Loader}
@impl true
def change(changeset, _opts, context) do
# Skip if already syncing to avoid recursion
if Map.get(context, :syncing_email, false) do
changeset
else
# around_transaction receives the changeset (cs) from Ash
# and a callback that executes the actual database operation
Ash.Changeset.around_transaction(changeset, fn cs, callback ->
result = callback.(cs)
with {:ok, user} <- Helpers.extract_record(result),
linked_member <- get_linked_member(user) do
Helpers.sync_email_to_linked_record(result, linked_member, user.email)
else
_ -> result
end
end)
override_email(changeset)
end
end
# Pattern match on nil member_id - no member linked
defp get_linked_member(%{member_id: nil}), do: nil
defp override_email(changeset) do
Ash.Changeset.around_transaction(changeset, fn cs, callback ->
result = callback.(cs)
# Load linked member by ID
defp get_linked_member(%{member_id: id}) do
case Ash.get(Mv.Membership.Member, id) do
{:ok, member} -> member
{:error, _} -> nil
end
with {:ok, user} <- Helpers.extract_record(result),
linked_member <- Loader.get_linked_member(user) do
Helpers.sync_email_to_linked_record(result, linked_member, user.email)
else
_ -> result
end
end)
end
end

View file

@ -1,55 +1,32 @@
defmodule Mv.Accounts.User.Changes.SyncEmailToMember do
@moduledoc """
Synchronizes user email changes to the linked member.
When a user's email is updated and the user is linked to a member,
this change automatically updates the member's email to match.
This ensures bidirectional email synchronization with User.email
as the source of truth.
Uses `around_transaction` to guarantee atomicity - both the user
and member updates happen in the SAME database transaction.
Uses `around_transaction` for atomicity - both updates in the same transaction.
"""
use Ash.Resource.Change
alias Mv.EmailSync.Helpers
alias Mv.EmailSync.{Helpers, Loader}
@impl true
def change(changeset, _opts, context) do
cond do
# Skip if already syncing to avoid recursion
Map.get(context, :syncing_email, false) ->
changeset
# Only proceed if email is actually changing
not Ash.Changeset.changing_attribute?(changeset, :email) ->
changeset
# Apply the sync logic
true ->
new_email = Ash.Changeset.get_attribute(changeset, :email)
# around_transaction receives the changeset (cs) from Ash
# and a callback that executes the actual database operation
Ash.Changeset.around_transaction(changeset, fn cs, callback ->
result = callback.(cs)
with {:ok, user} <- Helpers.extract_record(result),
linked_member <- get_linked_member(user) do
Helpers.sync_email_to_linked_record(result, linked_member, new_email)
else
_ -> result
end
end)
Map.get(context, :syncing_email, false) -> changeset
not Ash.Changeset.changing_attribute?(changeset, :email) -> changeset
true -> sync_email(changeset)
end
end
defp get_linked_member(%{member_id: nil}), do: nil
defp sync_email(changeset) do
new_email = Ash.Changeset.get_attribute(changeset, :email)
defp get_linked_member(%{member_id: member_id}) do
case Ash.get(Mv.Membership.Member, member_id) do
{:ok, member} -> member
{:error, _} -> nil
end
Ash.Changeset.around_transaction(changeset, fn cs, callback ->
result = callback.(cs)
with {:ok, user} <- Helpers.extract_record(result),
linked_member <- Loader.get_linked_member(user) do
Helpers.sync_email_to_linked_record(result, linked_member, new_email)
else
_ -> result
end
end)
end
end