Skip to main content

lib/attached/variants/variant_transform_worker.ex

defmodule Attached.Variants.VariantTransformWorker do
  @moduledoc """
  Oban worker that pre-processes a variant asynchronously.

  Useful for eagerly warming up variants after upload so the first
  `Attached.url/3` call returns the cached result immediately.

  ## Enqueueing

      Attached.Variants.VariantTransformWorker.new(%{
        "original_id" => original.id,
        "record_module" => "MyApp.Accounts.User",
        "field" => "avatar",
        "variant" => "thumb"
      })
      |> Oban.insert()

  The worker resolves the transform spec from the record module's schema
  declaration at perform time — no transform serialization, no
  string-to-atom round-trip on user-supplied keys.
  """

  use Oban.Worker, queue: :default, max_attempts: 3

  @impl true
  def perform(%Oban.Job{
        args: %{
          "original_id" => original_id,
          "record_module" => record_module,
          "field" => field,
          "variant" => variant
        }
      }) do
    module = Module.concat([record_module])
    field = lookup_atom!(module.__attached_fields__(), field, "field")

    transforms_for_variants = module.__attached_variants__(field)
    variant = lookup_atom!(Map.keys(transforms_for_variants), variant, "variant")

    transforms =
      transforms_for_variants
      |> Map.fetch!(variant)
      |> Keyword.put(:variant_name, variant)

    transform_digest = Attached.Variants.transform_digest(transforms)
    original = Attached.Originals.get!(original_id)

    Attached.Variants.process(original, transform_digest, transforms)
    :ok
  end

  # Resolves a string from the Oban payload back to one of the atoms the
  # schema declared. Avoids `String.to_existing_atom` — the atoms we accept
  # are exactly the ones the module exposes, no broader.
  defp lookup_atom!(known, input, label) do
    Enum.find(known, fn atom -> Atom.to_string(atom) == input end) ||
      raise ArgumentError,
            "unknown #{label}: #{inspect(input)} — known: #{inspect(known)}"
  end
end