1.9 KiB · text History b868907
defmodule GitGud.Repo.Migrations.AddUserScopedRunners do
  use Ecto.Migration

  # A fourth runner scope: a user's own, serving the repositories they
  # own personally. Registered with a token minted on
  # /users/settings/runners, which works like a repository's or an
  # organization's: single use, expiring.
  #
  # `runners.owner_id` becomes that scope. The column has been there
  # since the table was created but nothing ever wrote or read it, so
  # whatever it holds means nothing — and from here on a runner with an
  # owner only takes jobs from that user's repositories. Clear it first
  # so no global runner quietly shrinks to one person's.
  def up do
    alter table(:users) do
      add :runner_reg_token_hash, :binary
      add :runner_reg_token_expires_at, :utc_datetime
    end

    create unique_index(:users, [:runner_reg_token_hash],
             where: "runner_reg_token_hash IS NOT NULL",
             name: :users_runner_reg_token_hash_index
           )

    execute "UPDATE runners SET owner_id = NULL WHERE owner_id IS NOT NULL"

    drop constraint(:runners, :runners_scope_xor)

    # At most one of the three; none is a global runner.
    create constraint(:runners, :runners_scope_xor,
             check: "num_nonnulls(repository_id, organization_id, owner_id) <= 1"
           )
  end

  def down do
    drop constraint(:runners, :runners_scope_xor)

    # A user's runner has no scope the old schema knows; dropping it is
    # safer than letting it fall back to global.
    execute "DELETE FROM runners WHERE owner_id IS NOT NULL"

    create constraint(:runners, :runners_scope_xor,
             check: "(repository_id IS NULL) OR (organization_id IS NULL)"
           )

    drop index(:users, [:runner_reg_token_hash], name: :users_runner_reg_token_hash_index)

    alter table(:users) do
      remove :runner_reg_token_hash
      remove :runner_reg_token_expires_at
    end
  end
end