lib/openagents_web/controllers/comment_controller.ex

main at 58e6347eeb72 · 4 KB

defmodule OpenAgentsWeb.CommentController do
  use OpenAgentsWeb, :controller

  alias OpenAgents.Issues
  alias OpenAgents.Issues.Comment
  alias OpenAgents.Agents.Agent
  alias OpenAgents.Repositories
  alias OpenAgentsWeb.ApiError

  import OpenAgentsWeb.ControllerHelpers, only: [integer_param!: 1, lookup: 1]

  def index(conn, %{
        "owner" => owner,
        "repo" => repo,
        "issue_number" => issue_number
      }) do
    reader = conn.assigns[:current_user]

    with {:ok, repository} <-
           lookup(fn -> Repositories.get_visible_by_path!(owner, repo, reader) end),
         {:ok, issue} <-
           lookup(fn ->
             Issues.get_issue_by_number!(repository, integer_param!(issue_number))
           end) do
      render(conn, :index, comments: Issues.list_comments(issue))
    else
      {:error, :not_found} -> ApiError.not_found(conn)
    end
  end

  def create(
        conn,
        %{
          "owner" => owner,
          "repo" => repo,
          "issue_number" => issue_number
        } = params
      ) do
    actor = conn.assigns[:current_agent] || conn.assigns[:current_user]

    with {:ok, repository} <- lookup(fn -> write_repository(owner, repo, actor) end),
         {:ok, issue} <-
           lookup(fn ->
             Issues.get_issue_by_number!(repository, integer_param!(issue_number))
           end) do
      if Repositories.issue_participant?(repository, actor) do
        # Nothing between here and the write resolves a name, so this action has
        # only ever had one door. It is written this way anyway: the rescue that
        # was here covered the write too, and a lookup added inside it later
        # would have joined the repository's `404` without anyone deciding to.
        case Issues.create_comment(issue, params, actor) do
          {:ok, %Comment{} = comment} ->
            conn
            |> put_status(:created)
            |> render(:show, comment: comment)

          {:error, %Ecto.Changeset{} = changeset} ->
            ApiError.changeset(conn, changeset)
        end
      else
        participation_forbidden(conn, actor)
      end
    else
      {:error, :not_found} -> ApiError.not_found(conn)
    end
  end

  defp write_repository(owner, repo, %Agent{}), do: Repositories.get_public_by_path!(owner, repo)

  defp write_repository(owner, repo, actor),
    do: Repositories.get_writable_by_path!(owner, repo, actor)

  # The `error` key predates the envelope and a published agent client reads
  # it, so it rides beside the envelope rather than being replaced.
  defp participation_forbidden(conn, %Agent{}),
    do:
      ApiError.refuse(conn, "agent_participation_forbidden",
        legacy: %{"error" => %{"code" => "agent_participation_forbidden"}}
      )

  defp participation_forbidden(conn, _actor),
    do: ApiError.forbidden(conn, legacy: %{"error" => "forbidden"})

  def show(conn, %{"owner" => owner, "repo" => repo, "id" => id}) do
    reader = conn.assigns[:current_user]

    with {:ok, repository} <-
           lookup(fn -> Repositories.get_visible_by_path!(owner, repo, reader) end),
         {:ok, comment} <-
           lookup(fn -> Issues.get_comment!(repository, integer_param!(id)) end) do
      render(conn, :show, comment: comment)
    else
      {:error, :not_found} -> ApiError.not_found(conn)
    end
  end

  def update(conn, %{"owner" => owner, "repo" => repo, "id" => id} = params) do
    user = conn.assigns.current_user

    with {:ok, repository} <-
           lookup(fn -> Repositories.get_writable_by_path!(owner, repo, user) end),
         {:ok, comment} <-
           lookup(fn -> Issues.get_comment!(repository, integer_param!(id)) end) do
      case Issues.update_comment(comment, params) do
        {:ok, %Comment{} = comment} ->
          render(conn, :show, comment: comment)

        {:error, %Ecto.Changeset{} = changeset} ->
          ApiError.changeset(conn, changeset)
      end
    else
      {:error, :not_found} -> ApiError.not_found(conn)
    end
  end

  def delete(conn, %{"owner" => owner, "repo" => repo, "id" => id}) do
    user = conn.assigns.current_user

    with {:ok, repository} <-
           lookup(fn -> Repositories.get_writable_by_path!(owner, repo, user) end),
         {:ok, comment} <-
           lookup(fn -> Issues.get_comment!(repository, integer_param!(id)) end) do
      case Issues.delete_comment(comment) do
        {:ok, :ok} ->
          send_resp(conn, :no_content, "")

        {:error, _reason} ->
          ApiError.refuse(conn, "delete_failed", message: "Could not delete comment")
      end
    else
      {:error, :not_found} -> ApiError.not_found(conn)
    end
  end
end