prop/vendor/oban_pro/lib/oban/pro/cron.ex
Graham McIntire e99bf06eb4
deps: re-vendor oban_pro 1.7.0 (revert hex-repo dep)
Previous commit (3c988a5f) switched oban_pro to the licensed hex
repo at deploy time. Reverting that — keep all three Pro packages
vendored so prod image builds don't depend on oban.pro reachability
or auth on every CI run.

oban_pro 1.7.0 dropped into vendor/oban_pro via
`mix hex.package fetch oban_pro 1.7.0 --repo=oban --unpack`. mix.exs
goes back to `path: "vendor/oban_pro"` (matching oban_met / oban_web,
which were already vendored — and stay vendored since the licensed
repo only has older versions of those: 0.1.11 / 2.10.6 vs the
1.1.0 / 2.12.1 we vendor).

Schema migration from 3c988a5f stays — 1.7.0 tables/indexes are
already applied. No code changes.
2026-04-30 09:29:55 -05:00

125 lines
3.2 KiB
Elixir

defmodule Oban.Pro.Cron do
@moduledoc false
use Ecto.Schema
import Ecto.Changeset
alias Oban.Cron.Expression
alias Oban.Worker
@primary_key {:name, :string, autogenerate: false}
schema "oban_crons" do
field :expression, :string
field :worker, :string
field :opts, :map
field :paused, :boolean
field :insertions, {:array, :utc_datetime_usec}
field :lock_version, :integer, default: 1
field :parsed, :any, virtual: true
timestamps(
inserted_at: :inserted_at,
updated_at: :updated_at,
type: :utc_datetime_usec
)
end
@permitted ~w(name expression insertions worker opts paused)a
@requried ~w(name expression worker)a
@allowed_opts ~w(args guaranteed max_attempts meta priority queue tags timezone)a
@doc false
def allowed_opts, do: @allowed_opts
@spec changeset({binary(), module()} | {binary(), module(), Keyword.t()}) :: Ecto.Changeset.t()
def changeset({expression, worker}) do
params = %{expression: expression, name: worker, worker: worker, opts: %{}}
changeset(%__MODULE__{}, params)
end
def changeset({expression, worker, opts}) do
{name, opts} = Keyword.pop(opts, :name, worker)
{paused, opts} = Keyword.pop(opts, :paused)
{insertions, opts} = Keyword.pop(opts, :insertions)
params = %{
expression: expression,
insertions: insertions,
name: name,
opts: Map.new(opts),
paused: paused,
worker: worker
}
changeset(%__MODULE__{}, params)
end
@spec changeset({Ecto.Schema.t(), map()}) :: Ecto.Changeset.t()
def changeset(schema, params) when is_map(params) do
params =
params
|> coerce_name(:name)
|> coerce_name(:worker)
|> merge_opts(schema)
schema
|> cast(params, @permitted)
|> validate_required(@requried)
|> validate_change(:expression, &expression_validator/2)
|> validate_change(:opts, &opts_validator/2)
|> optimistic_lock(:lock_version)
end
def update_changeset(schema, params) when is_map(params) do
if Map.has_key?(params, :expression) or Map.has_key?(params, :timezone) do
changeset(schema, Map.put(params, :insertions, []))
else
changeset(schema, params)
end
end
defp coerce_name(params, key) do
case params do
%{^key => value} when is_atom(value) ->
Map.put(params, key, Worker.to_string(value))
_ ->
params
end
end
defp merge_opts(params, schema) do
case Map.split(params, @allowed_opts) do
{opts, params} when map_size(opts) > 0 ->
opts = Map.new(opts, fn {key, val} -> {to_string(key), val} end)
Map.put(params, :opts, Map.merge(schema.opts, opts))
_ ->
params
end
end
# Validators
defp expression_validator(:expression, expression) do
Expression.parse!(expression)
[]
rescue
ArgumentError ->
[expression: "expected cron expression to be a parsable binary"]
end
defp opts_validator(:opts, opts) do
string_keys = Enum.map(@allowed_opts, &to_string/1)
if Enum.all?(opts, fn {key, _} -> to_string(key) in string_keys end) do
[]
else
[opts: "expected cron opts to be one of #{inspect(@allowed_opts)}"]
end
end
end