lib/ex_teal/router.ex

defmodule ExTeal.Router do
  @moduledoc """
  A `Plug.Router`.  This module is meant to be plugged into host applications.

  See the readme for more details.
  """

  use Plug.Router

  alias ExTeal.Api.{
    CardResponder,
    ManyToMany,
    MetricResponder,
    ResourceResponder
  }

  alias ExTeal.{GlobalSearch, View}
  alias ExTeal.Resource.Serializer
  alias Plug.Conn

  if Mix.env() == :dev do
    use Plug.Debugger, otp_app: :ex_teal
  end

  plug(Plug.Static,
    at: "/",
    gzip: true,
    brotli: true,
    from: {:ex_teal, "priv/static/teal"}
  )

  plug(Plug.Logger, log: :debug)

  plug(Plug.Parsers, parsers: [:urlencoded])
  plug(Plug.MethodOverride)

  plug(:match)
  plug(:dispatch)

  @doc false
  def call(conn, opts) do
    conn =
      conn
      |> extract_namespace(opts)
      |> skip_csrf_for_assets(conn.path_info)

    super(conn, opts)
  end

  @endpoints_to_protect ~w(api plugins search)

  defp skip_csrf_for_assets(conn, [first | _]) when first in @endpoints_to_protect, do: conn

  defp skip_csrf_for_assets(conn, _) do
    Conn.put_private(conn, :plug_skip_csrf_protection, true)
  end

  get "/api/configuration" do
    config = ExTeal.json_configuration(conn) |> Jason.encode!()
    Serializer.as_json(conn, config, 200)
  end

  get "/search" do
    resources = Enum.filter(ExTeal.searchable_resources(), & &1.policy().view_any?(conn))

    conn
    |> GlobalSearch.new(resources)
    |> GlobalSearch.run()
    |> GlobalSearch.render()
  end

  get("/export/:resource_name", do: ResourceResponder.export(conn, resource_name))

  get("/api/dashboards/:name", do: CardResponder.dashboard(conn, name))

  get("/api/:resource_name/cards", do: CardResponder.resource(conn, resource_name))

  get("/api/metrics/:uri", do: MetricResponder.get(conn, uri))

  get("/api/:resource_name/metrics/:uri",
    do: MetricResponder.resource_index(conn, resource_name, uri)
  )

  get("/api/:resource_name/:resource_id/metrics/:uri",
    do: MetricResponder.resource_detail(conn, resource_name, resource_id, uri)
  )

  get("/api/:resource_name", do: ResourceResponder.index(conn, resource_name))

  get("/api/:resource_name/creation-fields",
    do: ResourceResponder.creation_fields(conn, resource_name)
  )

  get("/api/:resource_name/field/:field_name",
    do: ResourceResponder.field(conn, resource_name, field_name)
  )

  get("/api/:resource_name/field-filters",
    do: ResourceResponder.field_filters(conn, resource_name)
  )

  get("/api/:resource_name/:resource_id/attachable/:field_name",
    do: ManyToMany.attachable(conn, resource_name, resource_id, field_name)
  )

  post("/api/:resource_name/:resource_id/attach/:field_name",
    do: ManyToMany.attach(conn, resource_name, resource_id, field_name)
  )

  delete("/api/:resource_name/:resource_id/detach/:field_name",
    do: ManyToMany.detach(conn, resource_name, resource_id, field_name)
  )

  get("/api/:resource_name/creation-pivot-fields/:field_name",
    do: ManyToMany.creation_pivot_fields(conn, resource_name, field_name)
  )

  get("/api/:resource_name/:resource_id/update-pivot-fields/:field_name/:related_resource_id",
    do:
      ManyToMany.update_pivot_fields(
        conn,
        resource_name,
        resource_id,
        field_name,
        related_resource_id
      )
  )

  put("/api/:resource_name/:resource_id/update-pivot/:field_name/:related_resource_id",
    do:
      ManyToMany.update_pivot(
        conn,
        resource_name,
        resource_id,
        field_name,
        related_resource_id
      )
  )

  put("/api/:resource_name/:resource_id/reorder/:field_name",
    do:
      ManyToMany.reorder(
        conn,
        resource_name,
        resource_id,
        field_name
      )
  )

  get("/api/:resource_name/actions", do: ResourceResponder.actions_for(conn, resource_name))
  post("/api/:resource_name/actions", do: ResourceResponder.commit_action(conn, resource_name))

  get("/api/:resource_name/:resource_id/update-fields",
    do: ResourceResponder.update_fields(conn, resource_name, resource_id)
  )

  get("/api/:resource_name/relatable/:relationship_name",
    do: ResourceResponder.relatable(conn, resource_name, relationship_name)
  )

  get("/api/:resource_name/:resource_id",
    do: ResourceResponder.show(conn, resource_name, resource_id)
  )

  post("/api/:resource_name/",
    do: ResourceResponder.create(conn, resource_name)
  )

  put("/api/:resource_name/reorder", do: ResourceResponder.reorder(conn, resource_name))

  put("/api/:resource_name/:resource_id",
    do: ResourceResponder.update(conn, resource_name, resource_id)
  )

  delete("/api/:resource_name",
    do: ResourceResponder.delete(conn, resource_name)
  )

  forward("/plugins/:uri", to: ExTeal.Api.PluginResponder)

  match _ do
    conn
    |> put_resp_header("content-type", "text/html; charset=utf-8")
    |> Conn.send_resp(200, View.render(conn))
    |> halt()
  end

  defp extract_namespace(conn, opts) do
    ns = opts[:namespace] || ""
    Conn.assign(conn, :namespace, "/" <> ns)
  end
end