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
endOptions
:api—:json_apior: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.
Summary
Functions
@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)