lib/openagents/issues/closing_reference.ex

main at 58e6347eeb72 · 2 KB

defmodule OpenAgents.Issues.ClosingReference do
  @moduledoc """
  One `Closes #N` reference a commit made to an issue in the same repository
  (`issue_closing_references`).

  The row is the record of the reference, not of the close: `closed` says
  whether this reference is the one that moved the issue to `closed`, and is
  `false` when the issue was already closed. Keeping the reference either way
  is what lets the issue page link back to the commit that shipped it even
  when someone closed it by hand first.

  The row is also the idempotency gate. WAL replay, `reconcile_receipts/1`,
  and a force push that re-presents the same commits all reach the same
  `{issue_id, commit_sha}` pair, and the unique index turns the second
  attempt into a no-op rather than a second close.
  """

  use Ecto.Schema
  import Ecto.Changeset

  alias OpenAgents.Accounts.User
  alias OpenAgents.Issues.Issue
  alias OpenAgents.Repositories.Repository

  @type t :: %__MODULE__{}

  @primary_key {:id, :binary_id, autogenerate: true}
  @timestamps_opts [type: :utc_datetime_usec]

  schema "issue_closing_references" do
    field :commit_sha, :string
    field :repo, :string
    field :wal_seq, :integer
    field :principal, :string
    field :verb, :string
    field :closed, :boolean, default: false

    belongs_to :repository, Repository, type: :binary_id
    belongs_to :issue, Issue
    belongs_to :closed_by_user, User, type: :binary_id
    field :push_receipt_id, :binary_id

    timestamps(updated_at: false)
  end

  @doc false
  def changeset(reference, attrs) do
    reference
    |> cast(attrs, [
      :repository_id,
      :issue_id,
      :commit_sha,
      :repo,
      :wal_seq,
      :principal,
      :verb,
      :closed,
      :closed_by_user_id,
      :push_receipt_id
    ])
    |> validate_required([:repository_id, :issue_id, :commit_sha, :principal])
    |> validate_format(:commit_sha, ~r/\A[0-9a-f]{7,64}\z/)
    |> unique_constraint([:issue_id, :commit_sha])
    |> foreign_key_constraint(:repository_id)
    |> foreign_key_constraint(:issue_id)
    |> foreign_key_constraint(:closed_by_user_id)
  end
end