lib/openagents_web/controllers/artifact_listing_admin_controller.ex

58e6347eeb72 · 3 KB

defmodule OpenAgentsWeb.ArtifactListingAdminController do
  use OpenAgentsWeb, :controller

  alias OpenAgents.ArtifactCatalog
  alias OpenAgents.ArtifactCatalog.Listing
  alias OpenAgents.ArtifactCatalog.Receipt

  def create(conn, params) do
    case ArtifactCatalog.publish_listing(params) do
      {:ok, listing} ->
        conn
        |> put_status(:created)
        |> json(%{"listing" => Listing.public_projection(listing)})

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

  def delete(conn, %{"id" => id} = params) do
    attributes =
      params
      |> Map.put_new("actor_ref", conn.assigns.current_user.github_login)
      |> Map.put_new("receipt_ref", "artifact-removal:#{Ecto.UUID.generate()}")

    case ArtifactCatalog.remove_listing(id, attributes) do
      {:ok, listing} ->
        json(conn, %{
          "listing_id" => listing.id,
          "listing_digest" => listing.listing_digest,
          "state" => listing.state,
          "removed_at" => listing.removed_at
        })

      {:error, reason} ->
        operation_error(conn, reason)
    end
  end

  def export(conn, %{"id" => id}) do
    case ArtifactCatalog.export_listing_history(id) do
      {:ok, export} ->
        conn
        |> put_resp_header(
          "content-disposition",
          ~s(attachment; filename="artifact-listing-history-#{id}.json")
        )
        |> json(export)

      {:error, reason} ->
        operation_error(conn, reason)
    end
  end

  def record(conn, %{"id" => id, "action" => action} = params) do
    case ArtifactCatalog.record_transaction(id, action, params) do
      {:ok, %Receipt{} = receipt} ->
        conn
        |> put_status(:created)
        |> json(%{"receipt" => Receipt.projection(receipt)})

      {:error, reason} ->
        operation_error(conn, reason)
    end
  end

  def authorize(conn, %{"id" => id} = params) do
    case ArtifactCatalog.authorize_source_access(id, params) do
      {:ok, authorization} ->
        json(conn, %{"authorization" => authorization})

      {:error, reason} ->
        operation_error(conn, reason)
    end
  end

  defp validation_error(conn, changeset) do
    errors =
      Ecto.Changeset.traverse_errors(changeset, fn {message, options} ->
        Enum.reduce(options, message, fn {key, value}, text ->
          String.replace(text, "%{#{key}}", to_string(value))
        end)
      end)

    conn
    |> put_status(:unprocessable_entity)
    |> json(%{"error" => "invalid_listing", "fields" => errors})
  end

  defp operation_error(conn, reason) do
    status =
      case reason do
        :not_found -> :not_found
        :not_authorized -> :forbidden
        :listing_removed -> :conflict
        :stale_license -> :conflict
        {:digest_mismatch, _field} -> :unprocessable_entity
        {:invalid_predecessor, _action} -> :unprocessable_entity
        %Ecto.Changeset{} -> :unprocessable_entity
        _other -> :unprocessable_entity
      end

    conn
    |> put_status(status)
    |> json(%{"error" => error_code(reason)})
  end

  defp error_code({:digest_mismatch, field}), do: "digest_mismatch:#{field}"
  defp error_code({:invalid_predecessor, action}), do: "invalid_predecessor:#{action}"
  defp error_code(%Ecto.Changeset{}), do: "invalid_receipt"
  defp error_code(reason) when is_atom(reason), do: Atom.to_string(reason)
end