defmodule Microwaveprop.ContextHelpers do @moduledoc """ Shared helpers for context modules: owner-scoped fetches, safe Oban enqueuing, and UUID casting. """ alias Microwaveprop.Accounts.User alias Microwaveprop.Repo @doc """ Fetches a record by id, scoped to the given user. Admins bypass ownership — they can fetch any record. Regular users can only fetch records where `user_id` matches. Validates that `id` is a UUID before hitting the database; non-UUID strings return `{:error, :not_found}` instead of raising CastError. Returns `{:ok, record}` or `{:error, :not_found}`. ## Examples fetch_owned(MySchema, "some-uuid", admin_user) fetch_owned(MySchema, "some-uuid", regular_user) """ @spec fetch_owned(module(), Ecto.UUID.t(), User.t()) :: {:ok, Ecto.Schema.t()} | {:error, :not_found} def fetch_owned(schema, id, user) do with {:ok, uuid} <- cast_uuid(id) do do_fetch_owned(schema, uuid, user) end end defp do_fetch_owned(schema, uuid, %User{is_admin: true}) do case Repo.get(schema, uuid) do nil -> {:error, :not_found} record -> {:ok, record} end end defp do_fetch_owned(schema, uuid, %User{id: user_id}) do case Repo.get(schema, uuid) do %{user_id: ^user_id} = record -> {:ok, record} _ -> {:error, :not_found} end end @doc """ Safe Oban enqueue — only enqueues if the worker module is loaded and exports `new/1`. Returns whatever `Oban.insert/1` returns on success, or `{:error, :worker_not_available}` if the worker is not available. """ @spec safe_enqueue(module(), map()) :: {:ok, Oban.Job.t()} | {:error, :worker_not_available | Ecto.Changeset.t()} def safe_enqueue(worker, args \\ %{}) do if Code.ensure_loaded?(worker) and function_exported?(worker, :new, 1) do args |> worker.new() |> Oban.insert() else {:error, :worker_not_available} end end @doc """ Safely casts a string to a UUID. Returns `{:ok, uuid}` or `:error`. Use this at API/controller boundaries so contexts never receive bad UUIDs. """ @spec cast_uuid(binary()) :: {:ok, Ecto.UUID.t()} | :error def cast_uuid(id) when is_binary(id), do: Ecto.UUID.cast(id) def cast_uuid(_), do: :error end