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