# `MishkaCmsCore.ExtensionRouter`
[🔗](https://github.com/mishka-group/mishka_cms_core/blob/v0.0.1/lib/mishka_cms_core/extension_router.ex#L5)

Behaviour and macro for extensions to define their routes.

Extensions implement this behaviour to register their admin, public, and API routes.
The main CMS router uses `extension_routes/1` macro to load all extension routes.

## Usage

In your extension:

    defmodule MishkaBlogWeb.Routes do
      @behaviour MishkaCmsCore.ExtensionRouter

      @impl true
      def routes(:admin) do
        [
          {:live, "/blog/posts", MishkaBlogWeb.Live.Admin.PostLive, :index}
        ]
      end

      def routes(_), do: []
    end

In your main router:

    import MishkaCmsCore.ExtensionRouter

    scope "/admin/dashboard" do
      pipe_through [:browser, :require_authenticated_user]

      live_session :admin do
        extension_routes(:admin)
      end

      live_session :admin_standalone do
        extension_routes(:admin_standalone)
      end
    end

In config.exs:

    config :mishka_cms, :extensions, [
      MishkaBlogWeb,
      MishkaDocumentWeb
    ]

# `route`

```elixir
@type route() ::
  {:live, String.t(), module(), atom()}
  | {:live, String.t(), module(), atom(), keyword()}
  | {:get | :post | :put | :patch | :delete, String.t(), module(), atom()}
  | {:get | :post | :put | :patch | :delete, String.t(), module(), atom(),
     keyword()}
```

# `route_type`

```elixir
@type route_type() :: :admin | :admin_standalone | :public | :api
```

# `routes`

```elixir
@callback routes(route_type()) :: [route()]
```

Returns the routes for the given type.

Types:
- `:admin` - Admin dashboard routes
- `:admin_standalone` - Admin routes that need a document of their own (see below)
- `:public` - Public-facing routes
- `:api` - API routes

## When a route belongs in `:admin_standalone`

Only when the page needs something in `<head>` that the rest of the admin does not.

The root layout decides `<head>` from the LiveView's assigns, and it renders once per HTTP
response. A live navigation does not produce one: the destination LiveView runs inside the
document the SOURCE page built, with the source page's head. A page whose stylesheets, scripts or
meta tags differ from its neighbours' therefore arrives half-dressed, with no error anywhere —
the page builder reached this way lost its Tailwind JIT and the shim that nonces what it creates,
and its code editor opened unstyled.

`:admin_standalone` routes are emitted into a `live_session` of their own, and LiveView will not
live-navigate across a `live_session` boundary: arriving is a full page load, and so is leaving,
which also sheds whatever the page installed. Navigation BETWEEN two routes in this list stays
live, which is what makes it the right home for a group of pages that share a head.

Everything else belongs in `:admin`. The boundary costs a page load in each direction, and a page
that is dressed like the rest of the admin has nothing to buy with it.

# `extension_routes`
*macro* 

Macro to inject all extension routes into your router.

Extensions are configured in `:mishka_cms, :extensions`.
This macro is evaluated at compile time - no runtime overhead.

# `route_accepts`

```elixir
@spec route_accepts(Plug.Conn.t(), [String.t()]) :: Plug.Conn.t()
```

`Phoenix.Controller.accepts/2` for a pipeline that pages and files share: the formats the matched
route names under its `:accepts` private, or `formats` for a route that names none.

Plug it in place of `plug :accepts, formats`, imported from this module:

```elixir
import MishkaCmsCore.ExtensionRouter

pipeline :browser do
  plug :route_accepts, ["html"]
  # ...
end
```

A route answering a file of another kind names what it answers, and every other route keeps
`formats` — so `/robots.txt` answers `Accept: text/plain` while a page asked the same is still
refused with a 406:

    {:get, "/robots.txt", MyExtWeb.RobotsController, :show,
     private: %{accepts: ["html", "txt", "xml"]}}

A route serving a stored file as it is names `:any`, and answers whatever `Accept` it is sent, as
`Plug.Static` does — a crawler asks for a gzipped sitemap as `application/x-gzip`, which no format
names:

    {:get, "/sitemap-:file", MyExtWeb.SitemapController, :file, private: %{accepts: :any}}

A route's `private` is on the conn before its pipeline runs, so this reads the route being served.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
