lib/openagents/issues/task_sync.ex

main at 58e6347eeb72 · 2 KB

defmodule OpenAgents.Issues.TaskSync do
  @moduledoc """
  One automatic task-list edit (`issue_task_syncs`).

  A row exists for every body the forge rewrote on its own: which issue or
  comment it changed, which issue changing state caused it, and whether the
  checkbox ended up checked. `OpenAgentsWeb.IssueShowLive` reads the rows for
  one issue and renders them into the same history feed as comments, closes,
  and commit references, so an edit nobody made by hand is still an edit
  somebody can see.

  `principal` is always `"system"`. The forge is the actor here, not the
  person whose close triggered the rewrite: they closed an issue, they did not
  edit anyone's tracking issue, and attributing the edit to them would put
  words in their mouth. There is no `closed_by_user_id` equivalent on purpose.

  There is no unique index, and that is deliberate. The idempotency gate for
  this mechanism is the rendered body itself: `OpenAgents.Issues.TaskReferences`
  writes only when `OpenAgents.Issues.TaskList.render/2` returns something
  different from what is stored, so a repeat produces no write and no row. A
  unique key over `{issue_id, reference_issue_id}` would be wrong instead of
  redundant, because closing, reopening, and closing an issue again are three
  real edits that the history should show three times.
  """

  use Ecto.Schema
  import Ecto.Changeset

  alias OpenAgents.Issues.{Comment, Issue}
  alias OpenAgents.Repositories.Repository

  @type t :: %__MODULE__{}

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

  schema "issue_task_syncs" do
    field :reference_number, :integer
    field :checked, :boolean, default: false
    field :principal, :string, default: "system"

    belongs_to :repository, Repository, type: :binary_id
    belongs_to :issue, Issue
    belongs_to :comment, Comment
    belongs_to :reference_issue, Issue

    timestamps(updated_at: false)
  end

  @doc false
  def changeset(sync, attrs) do
    sync
    |> cast(attrs, [
      :repository_id,
      :issue_id,
      :comment_id,
      :reference_issue_id,
      :reference_number,
      :checked,
      :principal
    ])
    |> validate_required([
      :repository_id,
      :issue_id,
      :reference_issue_id,
      :reference_number,
      :principal
    ])
    |> validate_number(:reference_number, greater_than: 0)
    |> foreign_key_constraint(:repository_id)
    |> foreign_key_constraint(:issue_id)
    |> foreign_key_constraint(:comment_id)
    |> foreign_key_constraint(:reference_issue_id)
  end
end