3.1 KiB · text History b868907
defmodule GitGudWeb.Api.Mobile.LabelController do
  @moduledoc """
  A repository's labels: the ones a pull request can be given.

  The list is `Labels.list_labels/1` — the repository's own labels and
  those it inherits from its organization, minus any it opted out of —
  so it's exactly the set the pull request page offers. Labels are
  attached to a pull request through `PullRequestController`.

  Creating one makes a repository label, as the repository's labels page
  does. That's repository configuration, so it takes a repository admin
  (`RepoLive.Header.admin?/2`, the rule behind the page's admin
  controls); organization labels are managed on the organization.
  """
  use GitGudWeb, :controller

  import GitGudWeb.Api.Mobile.Helpers

  alias GitGud.Labels
  alias GitGudWeb.Api.Mobile.Auth
  alias GitGudWeb.Api.Mobile.JSON

  # A neutral slate, for a label created without a colour.
  @default_color "#64748b"

  def index(conn, %{"owner" => owner, "name" => name}) do
    with {:ok, user} <- Auth.require_scope(conn, "pulls:read"),
         {:ok, repo} <- fetch_repo(conn, user, owner, name) do
      json(conn, %{"labels" => Enum.map(Labels.list_labels(repo), &JSON.repo_label/1)})
    else
      {:error, conn} -> conn
    end
  end

  @doc """
  Create a repository label: `name`, and optionally `color` (`#RRGGBB`)
  and `description`. A name the repository can already pick — its own
  or inherited — is refused rather than shadowed.
  """
  def create(conn, %{"owner" => owner, "name" => name} = params) do
    with {:ok, user} <- Auth.require_scope(conn, "pulls:write"),
         {:ok, repo} <- fetch_repo(conn, user, owner, name),
         :ok <- authorize_admin(conn, user, repo, owner, name),
         {:ok, label_name} <- require_name(conn, repo, params["label"]) do
      attrs = %{
        "name" => label_name,
        "color" => present(params["color"]) || @default_color,
        "description" => present(params["description"])
      }

      case Labels.create_label(repo, attrs) do
        {:ok, label} ->
          conn
          |> put_status(201)
          |> json(%{"label" => JSON.repo_label(Map.put(label, :inherited?, false))})

        {:error, %Ecto.Changeset{} = cs} ->
          send_error(conn, 422, changeset_message(cs))
      end
    else
      {:error, conn} -> conn
    end
  end

  defp authorize_admin(conn, user, repo, owner, name) do
    if GitGudWeb.RepoLive.Header.admin?(user, repo),
      do: :ok,
      else: {:error, send_error(conn, 403, "requires admin access to #{owner}/#{name}")}
  end

  defp require_name(conn, repo, raw) do
    label_name = String.trim(to_string(raw || ""))

    cond do
      label_name == "" ->
        {:error, send_error(conn, 422, "label can't be blank")}

      Enum.any?(Labels.list_labels(repo), &(&1.name == label_name)) ->
        {:error, send_error(conn, 422, "a label named #{label_name} already exists")}

      true ->
        {:ok, label_name}
    end
  end

  defp present(v) when is_binary(v) do
    case String.trim(v) do
      "" -> nil
      trimmed -> trimmed
    end
  end

  defp present(_), do: nil
end