lib/ex_teal/api/resource_responder.ex

defmodule ExTeal.Api.ResourceResponder do
  @moduledoc """
  Hands requests of a certain type to the appropriate resource,
  and returns the resources response as serialized json
  """
  alias ExTeal.Api.ErrorSerializer
  alias ExTeal.{FieldFilter, Panel}
  alias ExTeal.Resource.{Create, Delete, Export, Fields, Index, Serializer, Show, Update}

  @spec index(Plug.Conn.t(), binary) :: Plug.Conn.t()
  def index(conn, resource_uri) do
    with {:ok, resource} <- ExTeal.resource_for(resource_uri),
         true <- resource.policy().view_any?(conn) do
      Index.call(resource, conn)
    else
      error -> responder_error(conn, error)
    end
  end

  @spec show(Plug.Conn.t(), binary, binary) :: Plug.Conn.t()
  def show(conn, resource_uri, resource_id) do
    with {:ok, resource} <- ExTeal.resource_for(resource_uri),
         true <- resource.policy().view_any?(conn) do
      Show.call(resource, conn, resource_id)
    else
      error -> responder_error(conn, error)
    end
  end

  @spec creation_fields(Plug.Conn.t(), binary) :: Plug.Conn.t()
  def creation_fields(conn, resource_uri) do
    with {:ok, resource} <- ExTeal.resource_for(resource_uri),
         true <- resource.policy().create_any?(conn) do
      panels = Panel.gather_panels(resource)
      fields = Fields.fields_for(:new, resource)
      schema = resource.model()

      fields =
        fields
        |> Enum.map(fn field ->
          new_schema = struct(schema, %{})
          field.type.apply_options_for(field, new_schema, conn, :new)
        end)
        |> Panel.give_panel_to_fields(resource)

      {:ok, body} = Jason.encode(%{fields: fields, panels: panels})
      Serializer.as_json(conn, body, 200)
    else
      error -> responder_error(conn, error)
    end
  end

  @spec field(Plug.Conn.t(), binary, binary) :: Plug.Conn.t()
  def field(conn, resource_uri, field_name) do
    with {:ok, resource} <- ExTeal.resource_for(resource_uri),
         true <- resource.policy().view_any?(conn),
         {:ok, field} <- Fields.field_for(resource, field_name) do
      new_schema = resource.model() |> struct(%{})
      field = field.type.apply_options_for(field, new_schema, conn, :show)
      {:ok, body} = Jason.encode(%{field: field})
      Serializer.as_json(conn, body, 200)
    else
      error -> responder_error(conn, error)
    end
  end

  @spec update_fields(Plug.Conn.t(), binary, binary) :: Plug.Conn.t()
  def update_fields(conn, resource_uri, resource_id) do
    with {:ok, resource} <- ExTeal.resource_for(resource_uri),
         model <- resource.handle_show(conn, resource_id),
         true <- resource.policy().update?(conn, model) do
      fields =
        :edit
        |> Fields.fields_for(resource)
        |> Fields.apply_values(model, resource, conn, :edit, nil)
        |> Panel.give_panel_to_fields(resource)

      panels = Panel.gather_panels(resource)

      {:ok, body} = Jason.encode(%{fields: fields, panels: panels})
      Serializer.as_json(conn, body, 200)
    else
      error -> responder_error(conn, error)
    end
  end

  @spec relatable(Plug.Conn.t(), binary, binary) :: Plug.Conn.t()
  def relatable(conn, resource_uri, relationship) do
    with {:ok, resource} <- ExTeal.resource_for(resource_uri),
         true <- resource.policy().view_any?(conn),
         {:ok, related_resource} <- ExTeal.resource_for_relationship(resource, relationship) do
      Index.query_for_related(related_resource, conn)
    else
      error -> responder_error(conn, error)
    end
  end

  @spec filters_for(Plug.Conn.t(), binary) :: Plug.Conn.t()
  def filters_for(conn, resource_uri) do
    with {:ok, resource} <- ExTeal.resource_for(resource_uri),
         true <- resource.policy().view_any?(conn) do
      filters = resource.filters_for(conn)
      {:ok, body} = Jason.encode(%{filters: filters})
      Serializer.as_json(conn, body, 200)
    else
      error -> responder_error(conn, error)
    end
  end

  @spec actions_for(Plug.Conn.t(), binary) :: Plug.Conn.t()
  def actions_for(conn, resource_uri) do
    with {:ok, resource} <- ExTeal.resource_for(resource_uri),
         true <- resource.policy().view_any?(conn) do
      actions = resource.actions_for(conn)
      {:ok, body} = Jason.encode(%{actions: actions})
      Serializer.as_json(conn, body, 200)
    else
      error -> responder_error(conn, error)
    end
  end

  @spec commit_action(Plug.Conn.t(), binary) :: Plug.Conn.t()
  def commit_action(conn, resource_uri) do
    with {:ok, resource} <- ExTeal.resource_for(resource_uri),
         true <- resource.policy().view_any?(conn),
         {:ok, resp} <- ExTeal.Action.apply_action(resource, conn) do
      {:ok, body} = Jason.encode(resp)
      Serializer.as_json(conn, body, 200)
    else
      error -> responder_error(conn, error)
    end
  end

  @spec export(Plug.Conn.t(), binary) :: Plug.Conn.t()
  def export(conn, resource_uri) do
    with {:ok, resource} <- ExTeal.resource_for(resource_uri),
         true <- resource.policy().view_any?(conn) do
      Export.stream(resource, conn)
    else
      error -> responder_error(conn, error)
    end
  end

  @spec create(Plug.Conn.t(), binary) :: Plug.Conn.t()
  def create(conn, resource_uri) do
    with {:ok, resource} <- ExTeal.resource_for(resource_uri),
         true <- resource.policy().create_any?(conn) do
      Create.call(resource, conn)
    else
      error -> responder_error(conn, error)
    end
  end

  @spec update(Plug.Conn.t(), binary, binary) :: Plug.Conn.t()
  def update(conn, resource_uri, resource_id) do
    with {:ok, resource} <- ExTeal.resource_for(resource_uri),
         model <- resource.handle_show(conn, resource_id),
         true <- resource.policy().update?(conn, model) do
      Update.call(resource, resource_id, conn)
    else
      error -> responder_error(conn, error)
    end
  end

  @spec delete(Plug.Conn.t(), binary) :: Plug.Conn.t()
  def delete(conn, resource_uri) do
    with {:ok, resource} <- ExTeal.resource_for(resource_uri),
         true <- resource.policy().delete_any?(conn) do
      Delete.call(resource, conn)
    else
      error -> responder_error(conn, error)
    end
  end

  @spec reorder(Plug.Conn.t(), binary) :: Plug.Conn.t()
  def reorder(conn, resource_uri) do
    with {:ok, resource} <- ExTeal.resource_for(resource_uri),
         true <- resource.policy().update_any?(conn) do
      Update.batch_update(resource, conn)
    else
      error -> responder_error(conn, error)
    end
  end

  @spec field_filters(Plug.Conn.t(), binary) :: Plug.Conn.t()
  def field_filters(conn, resource_uri) do
    with {:ok, resource} <- ExTeal.resource_for(resource_uri),
         true <- resource.policy().view_any?(conn) do
      FieldFilter.for_resource(resource, conn)
    else
      error -> responder_error(conn, error)
    end
  end

  defp responder_error(conn, false), do: ErrorSerializer.handle_error(conn, :not_authorized)
  defp responder_error(conn, {:error, reason}), do: ErrorSerializer.handle_error(conn, reason)
end