# `MishkaCmsCore.Runtime.Plugs.ApiAvailabilityPlug`
[🔗](https://github.com/mishka-group/mishka_cms_core/blob/v0.0.1/lib/mishka_cms_core/runtime/plugs/api_availability_plug.ex#L5)

Closes a site's API while the site is offline, to everyone but its own API admins and the masters
(users whose `site_id` is nil) who are API admins: who holds anything under `admin:api`
(`MishkaCmsCore.Accounts.Helper.master_admin?/2` and `site_admin?/3` on the `:api` surface). A
dashboard-only admin reaches the site's screens, not its API, and is refused like anyone.

`MishkaCmsCore.Runtime.Plugs.ApiTenantLookupPlug` refuses a switched-off site before anybody is
known. This plug runs after `:load_from_bearer` and `:set_actor`, once the bearer token has named
the caller, and answers the offline switch — the site's own or the global one — by the rule
`MishkaCmsCore.Runtime.SiteAvailability.allowed_in?/4` gives on the `:api` surface. A token of another site is not loaded
for this one, so its holder counts as a visitor.

Per request this is ETS reads only: the site cache, the settings cache and the permissions the
bearer token's user was loaded with.

    pipeline :api do
      plug :accepts, ["json"]
      plug MishkaCmsCore.Runtime.Plugs.ApiTenantLookupPlug
      plug MishkaCmsCore.Runtime.Plugs.ApiCorsPlug
      plug :load_from_bearer
      plug :confirm_bearer_tenant
      plug :set_actor, :user
      plug MishkaCmsCore.Runtime.Plugs.ApiAvailabilityPlug, api: :json_api
    end

## Options

  * `:api` — `:json_api` or `:graphql`, the API whose error shape the refusal is written in.
  * `:at` — where the JSON:API router is mounted, `"/api/json"` by default.

## Signing in while offline

An admin has no token yet when the site is already offline, so these routes of
`MishkaCmsCore.Accounts.User` stay open to everyone on a JSON:API pipeline: sign-in
(`sign_in_with_password`), the password reset request and the reset itself
(`request_password_reset_token`, `reset_password_api`), `me` and `logout`. They are read from
`MishkaCmsCore.Accounts`' own routes, whole path and method, so a route renamed there follows and
one removed closes. Registering stays closed, and
`MishkaCmsCore.Accounts.Checks.RegistrationOpen` refuses it at the resource as well.

## The refusal

A `503` with `x-error-code: site_offline` and `cache-control: no-store`, in the API's own error
shape. The message is the site's `cms.maintenance_message`, or the global one. `ApiCorsPlug` runs
first, so a frontend on an origin the site allows can read it; it answers a preflight by itself.

JSON:API, as `application/vnd.api+json`:

    {"errors": [{"status": "503", "code": "site_offline", "title": "Site offline",
                 "detail": "We are currently undergoing maintenance. Please check back soon."}],
     "jsonapi": {"version": "1.0"}}

GraphQL:

    {"errors": [{"message": "We are currently undergoing maintenance. Please check back soon.",
                 "extensions": {"code": "site_offline", "status": 503}}]}

A site that is switched off between the tenant lookup and this plug is refused here as well, to
everyone and masters included, as the tenant lookup refuses it, with `website_unavailable`, the
code the tenant lookup uses for it.

## Events

- `api:request:site_unavailable` — the conn about to be refused, with `%{state: :offline | :inactive,
  site: site}` as its meta. A plugin may answer instead by halting the conn.

# `call`

# `init`

# `send_refusal`

```elixir
@spec send_refusal(
  Plug.Conn.t(),
  :offline | :inactive,
  map() | nil,
  :json_api | :graphql
) ::
  Plug.Conn.t()
```

Sends the refusal of `site` in `state` — `:offline` or `:inactive` — in `api`'s error shape, as
`call/2` does, with no event and no halt. `site` is `nil` for the global switch.

`MishkaCmsWeb.GraphqlSocket` answers a refused `/ws/gql` connect with it.

    ApiAvailabilityPlug.send_refusal(conn, :offline, site, :graphql)

---

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