- Create MaidenheadChangesetHelpers: consolidate grid validation, callsign normalization, lat/lon validation, grid/latlon derivation across 6 schemas - Create ContextHelpers: shared fetch_owned with admin bypass, safe_enqueue for Oban workers, UUID casting to replace CastError rescues - Extend LiveHelpers: add current_user/1 (removes 7 duplicate definitions), subscribe/2 (replaces 13 inline PubSub sites), assign_url_params/2 - Extract Propagation.ScoreStore (528 lines): separate file I/O and cache management from scoring logic, 13 defdelegate passthroughs - Split SubmitLive (942->475 lines): extract CSV/ADIF upload rendering into 3 function component modules (csv_upload, adif_upload, preview) - Update 16 LiveViews to use shared helpers
67 lines
2.2 KiB
Elixir
67 lines
2.2 KiB
Elixir
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
|