MishkaCmsCore.Runtime.SiteAvailability (Mishka CMS Core v0.0.1)

Copy Markdown View Source

Whether a site is up, and who may reach it when it is not.

A site is unavailable in two ways:

stateset onwho may reach it
:inactiveAdmin → Runtime → Sitesa master admin of the surface asked about
:offlineSettings → "Take this site offline" (the site's own or the global switch)its own admins and master admins of the surface asked about

A site the site cache does not know — archived, deleted, or not loaded — is :inactive, so a gate asking about it closes rather than opens.

Every answer is read from ETS: MishkaCmsCore.Runtime.States.SiteState for active, MishkaCmsCore.Settings.offline?/1 for the switch, and the permissions the user was loaded with. No query, no process call, so it can sit on every request.

An admin is an admin of a surface: the screens (:dashboard, the default) or the admin JSON:API and GraphQL (:api), or :any for either one. See MishkaCmsCore.Accounts.Helper.master_admin?/2 and MishkaCmsCore.Accounts.Helper.site_admin?/3 for who is who.

Open LiveViews

on_mount/4 closes an open LiveView the moment its site closes to its user: on a live navigation, on every event, and when changed/1 announces that the site's state changed. Add it to every LiveView:

on_mount MishkaCmsCore.Runtime.SiteAvailability

A view on a sign-in address (MishkaCmsCore.Runtime.Plugs.SiteLookupPlug.sign_in_path?/2, read from the address of every navigation) stays open while its site is offline, so the site's admins can sign in; every view closes on an inactive site. A refused view is redirected to the address it was on, a full page load that the pipeline answers.

A view that shows a record of another site — a preview, opened on any address — follows that site too, with follow/3 from its mount/3: it is refused there, and closed later, by the rule allowed?/2 answers for that site.

A view whose viewer the site's closed sign-in signs out (MishkaCmsCore.Accounts.AuthGate.signs_out?/2) reloads the address it was on the same way, and the pipeline signs them out there. The gate's settings call changed/1 as the offline switch does.

Events

  • runtime:state:site:availability_changed — %{key: site_id}, nil for every site, each time changed/1 is called.
  • web:live:site_unavailable — the socket an open LiveView is about to be sent away with, with %{site_id: site_id, path: path} as its meta. A plugin may redirect it elsewhere.

Summary

Functions

Whether user may reach the site site_id through surface — :dashboard (the default) for its pages, files, previews and screens, :api for its admin APIs, :any for either.

Tells this node's open LiveViews that site_id's state may have changed, and disconnects this node's sockets bound to it (socket_id/1) when it is no longer open; nil for every site.

Makes an open LiveView follow site_id as well as its host's site, from its mount/3.

Closes an open LiveView the moment its site closes to its user. See the moduledoc.

The ids of every site that is up: active, and not offline by its own or the global switch.

The id a socket bound to site_id answers to from Phoenix.Socket.id/1, as the GraphQL socket's does. changed/1 disconnects every socket under it once the site is no longer open, and each one that reconnects is asked again.

The site's state by its id.

Types

state()

@type state() :: :open | :offline | :inactive

Functions

allowed?(site_id, user, surface \\ :dashboard)

@spec allowed?(
  String.t() | nil,
  map() | nil,
  MishkaCmsCore.Accounts.Helper.admin_surface()
) ::
  boolean()

Whether user may reach the site site_id through surface — :dashboard (the default) for its pages, files, previews and screens, :api for its admin APIs, :any for either.

nil for user is a visitor who is not signed in.

SiteAvailability.allowed?(site.id, user)
SiteAvailability.allowed?(site.id, user, :api)

allowed_in?(state, site_id, user, surface \\ :dashboard)

@spec allowed_in?(
  state(),
  String.t() | nil,
  map() | nil,
  MishkaCmsCore.Accounts.Helper.admin_surface()
) ::
  boolean()

allowed?/3 for a state already read with state/1.

changed(site_id)

@spec changed(String.t() | nil) :: :ok

Tells this node's open LiveViews that site_id's state may have changed, and disconnects this node's sockets bound to it (socket_id/1) when it is no longer open; nil for every site.

SiteState and SettingsState call it after writing, so a view that checks again reads the new state. Local to the node, because each node's caches call it for their own write.

follow(socket, site_id, session)

@spec follow(Phoenix.LiveView.Socket.t(), String.t() | nil, map()) ::
  {:ok, Phoenix.LiveView.Socket.t()}
  | {:error, String.t() | nil, :offline | :inactive}

Makes an open LiveView follow site_id as well as its host's site, from its mount/3.

{:error, site_id, state} — the site that is closed to the viewer, the host's or site_id — when either is closed now; otherwise the view is closed as soon as one is. nil — a global record — follows the host's site only. Needs on_mount/4.

on_mount(atom, params, session, socket)

Closes an open LiveView the moment its site closes to its user. See the moduledoc.

open_site_ids()

@spec open_site_ids() :: [String.t()]

The ids of every site that is up: active, and not offline by its own or the global switch.

socket_id(site_id)

@spec socket_id(String.t()) :: String.t()

The id a socket bound to site_id answers to from Phoenix.Socket.id/1, as the GraphQL socket's does. changed/1 disconnects every socket under it once the site is no longer open, and each one that reconnects is asked again.

Needs the endpoint's PubSub server to be MishkaCmsCore.PubSub.

state(site_id)

@spec state(String.t() | nil) :: state()

The site's state by its id.

SiteAvailability.state(site.id)
#=> :offline