mitgliederverwaltung/lib/mv/email_sync/helpers.ex

93 lines
3 KiB
Elixir

defmodule Mv.EmailSync.Helpers do
@moduledoc """
Shared helper functions for email synchronization between User and Member.
Handles the complexity of `around_transaction` callback results and
provides clean abstractions for email updates within transactions.
"""
require Logger
import Ecto.Changeset
@doc """
Extracts the record from an Ash action result.
Handles both 2-tuple `{:ok, record}` and 4-tuple
`{:ok, record, changeset, notifications}` patterns.
"""
def extract_record({:ok, record, _changeset, _notifications}), do: {:ok, record}
def extract_record({:ok, record}), do: {:ok, record}
def extract_record({:error, _} = error), do: error
@doc """
Updates the result with a new record while preserving the original structure.
If the original result was a 4-tuple, returns a 4-tuple with the updated record.
If it was a 2-tuple, returns a 2-tuple with the updated record.
"""
def update_result_record({:ok, _old_record, changeset, notifications}, new_record) do
{:ok, new_record, changeset, notifications}
end
def update_result_record({:ok, _old_record}, new_record) do
{:ok, new_record}
end
@doc """
Updates an email field directly via Ecto within the current transaction.
This bypasses Ash's action system to ensure the update happens in the
same database transaction as the parent action.
"""
def update_email_via_ecto(record, new_email) do
record
|> cast(%{email: to_string(new_email)}, [:email])
|> Mv.Repo.update()
end
@doc """
Synchronizes email to a linked record if it exists.
Returns the original result unchanged, or an error if sync fails.
"""
def sync_email_to_linked_record(result, linked_record, new_email) do
with {:ok, _source} <- extract_record(result),
record when not is_nil(record) <- linked_record,
{:ok, _updated} <- update_email_via_ecto(record, new_email) do
# Successfully synced - return original result unchanged
result
else
nil ->
# No linked record - return original result
result
{:error, error} ->
# Sync failed - log and propagate error to rollback transaction
Logger.error("Email sync failed: #{inspect(error)}")
{:error, error}
end
end
@doc """
Overrides the record's email with the linked email if emails differ.
Returns updated result with new record, or original result if no update needed.
"""
def override_with_linked_email(result, linked_email) do
with {:ok, record} <- extract_record(result),
true <- record.email != to_string(linked_email),
{:ok, updated_record} <- update_email_via_ecto(record, linked_email) do
# Email was different - return result with updated record
update_result_record(result, updated_record)
else
false ->
# Emails already match - no update needed
result
{:error, error} ->
# Override failed - log and propagate error
Logger.error("Email override failed: #{inspect(error)}")
{:error, error}
end
end
end