MishkaCmsCore.Install.Bootstrap (Mishka CMS Core v0.0.1)

Copy Markdown View Source

Runs the first-run install: creates every permission name of permissions/0 that is missing, grants "*" to the master user (auto-confirmed, no email), creates the first (master) site, installs the chosen Chelekom components, writes the site's starter content — the theme, the header, the footer, the site-main layout and the home page (MishkaCmsCore.Install.StarterContent.seed!/2) — and its sign-in pages (MishkaCmsCore.Install.StarterContent.ensure_auth!/1), compiles the new site's runtime — exactly as a normal boot would — and flips the install flag. A name already stored is left as it is, so re-running after seeds is safe.

THE STARTER IS NOT A CHOICE. Every page of a site, an extension's included, is drawn in the site-main layout (MishkaCmsCore.Runtime.SiteLayout), so a site without it has no frame for any of them; and a first install whose / answers nothing is a broken one. The installer's box says so and cannot be unticked.

Everything runs authorize?: false because no privileged actor exists yet — which is why this is reachable only while the CMS is uninstalled (MishkaCms.Plugs.InstallGate).

The install runs in ordered phases (@steps); the installer renders one progress row per entry, so adding a phase here (with a matching run_step/3 clause) makes it appear in the UI automatically.

The master user is created via the existing register_with_password strategy action (which mints the token and hashes the password); with no tenant the user stays site_id: nil (a master), and authorize?: false relaxes the master-creation policy. The master is confirmed in-place and the changeset is tagged with mishka_install: true so the confirmation sender skips the email (see MishkaCmsCore.Accounts.User.Senders.SendNewUserConfirmationEmail).

Essential settings are written through the Settings façade with the category/source declared for each key in the registry, so each lands on the right tab of the settings page; unregistered keys are ignored.

The site's languages (:languages, codes MishkaCmsCore.Runtime.Languages.valid?/1 accepts, the first being the primary) are saved to the new site's own site.languages before any page is written, so every page the install writes answers each of them. A run given none leaves the site reading the global list. How its addresses name them (:language_urls, one of MishkaCmsCore.Runtime.Languages.url_modes/0) is saved beside them to its own site.language_urls; a run given none leaves the site reading the global setting.

See MishkaCmsCore.Install.

Summary

Functions

ensure_permissions(permissions \\ [{"*", "Every permission within the holder's own site. For a master that means every site. It never makes anyone a master."}, {"admin:*", "Full admin of the dashboard and the API."}, {"admin:dashboard:*", "Full dashboard admin: every dashboard area, every builder and code."}, {"admin:api:*", "Full API admin: every admin JSON:API and GraphQL area, and code."}, {"admin:dashboard:runtime:*", "Every runtime section on the dashboard, their code included, and opening every builder."}, {"admin:dashboard:runtime:site", "The Sites screen and site data: read, create, edit and archive. A site-wide action still needs a master."}, {"admin:dashboard:runtime:page", "Writing pages, their drafts and versions, their code included, and opening the page builder."}, {"admin:dashboard:runtime:layout", "Writing layouts, their drafts and versions, their code included, and opening the layout builder."}, {"admin:dashboard:runtime:component", "Writing components and their versions, their code included, and opening the component builder."}, {"admin:dashboard:runtime:error_page", "Writing error pages, their drafts and versions, their code included, and opening the error page builder."}, {"admin:dashboard:runtime:stylesheet", "Writing stylesheets, their CSS included."}, {"admin:dashboard:runtime:js_hook", "Writing JS hooks, their content included."}, {"admin:dashboard:runtime:media", "Writing media, media categories and their versions. Every dashboard admin reads them."}, {"admin:dashboard:runtime:translation", "Writing translation rows. Every dashboard admin reads them."}, {"admin:dashboard:runtime:redirect", "Writing the site's redirect rules. Every dashboard admin reads them."}, {"admin:dashboard:setting", "Writing site settings that declare no permission of their own, the extension switches and the notification toggles included."}, {"admin:dashboard:user", "The Users screen: list, read, create, edit and delete the accounts of the holder's own site."}, {"admin:dashboard:permission", "The Permissions screen: read the permission names, and grant or revoke permissions within what the holder holds."}, {"admin:dashboard:runtime:activity", "The Activity screen, activity entries and the recent-activity widget."}, {"admin:dashboard:code", "Writing pages, layouts, components, error pages, stylesheets and JS hooks, their code included, and opening every builder."}, {"admin:api:runtime:*", "Every runtime section over the admin JSON:API, their code included."}, {"admin:api:runtime:site", "Site data over the admin JSON:API."}, {"admin:api:runtime:page", "Writing pages over the admin JSON:API, their code included."}, {"admin:api:runtime:layout", "Writing layouts over the admin JSON:API, their code included."}, {"admin:api:runtime:component", "Writing components over the admin JSON:API, their code included."}, {"admin:api:runtime:error_page", "Writing error pages over the admin JSON:API, their code included."}, {"admin:api:runtime:stylesheet", "Writing stylesheets over the admin JSON:API, their CSS included."}, {"admin:api:runtime:js_hook", "Writing JS hooks over the admin JSON:API, their content included."}, {"admin:api:runtime:media", "Writing media and media categories over the admin JSON:API."}, {"admin:api:runtime:translation", "Writing translations over the admin JSON:API."}, {"admin:api:runtime:redirect", "Writing redirect rules over the admin JSON:API."}, {"admin:api:user", "Accounts over the admin JSON:API."}, {"admin:api:permission", "The permission names and the grants over the admin JSON:API."}, {"admin:api:runtime:activity", "The activity log over the admin JSON:API."}, {"admin:api:code", "Writing pages, layouts, components, error pages, stylesheets and JS hooks over the admin JSON:API, their code included."}])

Creates each name of permissions (the core's, permissions/0, when none are given) that is not stored yet. Returns every one of them, stored before or now, and the ones this call created. A stored name keeps its row and its description.

Every permission name the core install creates, with its description. An extension's own names are not here: its provision manifest ships them, and installing it creates them (MishkaCmsCore.Runtime.ExtensionProvision).

Runs the install phases in order, calling progress with {step_id, status, detail} as each phase starts (:running), emits a line (:log), finishes (:done) or fails (:error). Returns {:ok, %{user: user, site: site}} or {:error, reason} from the first failing phase. run/1 keeps the silent behaviour for non-interactive callers (seeds, tests).

The ordered install phases as {id, label} — one progress row per entry.

Types

params()

@type params() :: %{
  :email => String.t(),
  :password => String.t(),
  :site_name => String.t(),
  :host => String.t(),
  optional(:component_selection) => map(),
  optional(:languages) => [String.t()],
  optional(:language_urls) => String.t(),
  optional(:settings) => %{optional(String.t()) => term()},
  optional(:extensions) => [String.t()],
  optional(:extension_answers) => %{optional(String.t()) => map()}
}

Functions

ensure_permissions(permissions \\ [{"*", "Every permission within the holder's own site. For a master that means every site. It never makes anyone a master."}, {"admin:*", "Full admin of the dashboard and the API."}, {"admin:dashboard:*", "Full dashboard admin: every dashboard area, every builder and code."}, {"admin:api:*", "Full API admin: every admin JSON:API and GraphQL area, and code."}, {"admin:dashboard:runtime:*", "Every runtime section on the dashboard, their code included, and opening every builder."}, {"admin:dashboard:runtime:site", "The Sites screen and site data: read, create, edit and archive. A site-wide action still needs a master."}, {"admin:dashboard:runtime:page", "Writing pages, their drafts and versions, their code included, and opening the page builder."}, {"admin:dashboard:runtime:layout", "Writing layouts, their drafts and versions, their code included, and opening the layout builder."}, {"admin:dashboard:runtime:component", "Writing components and their versions, their code included, and opening the component builder."}, {"admin:dashboard:runtime:error_page", "Writing error pages, their drafts and versions, their code included, and opening the error page builder."}, {"admin:dashboard:runtime:stylesheet", "Writing stylesheets, their CSS included."}, {"admin:dashboard:runtime:js_hook", "Writing JS hooks, their content included."}, {"admin:dashboard:runtime:media", "Writing media, media categories and their versions. Every dashboard admin reads them."}, {"admin:dashboard:runtime:translation", "Writing translation rows. Every dashboard admin reads them."}, {"admin:dashboard:runtime:redirect", "Writing the site's redirect rules. Every dashboard admin reads them."}, {"admin:dashboard:setting", "Writing site settings that declare no permission of their own, the extension switches and the notification toggles included."}, {"admin:dashboard:user", "The Users screen: list, read, create, edit and delete the accounts of the holder's own site."}, {"admin:dashboard:permission", "The Permissions screen: read the permission names, and grant or revoke permissions within what the holder holds."}, {"admin:dashboard:runtime:activity", "The Activity screen, activity entries and the recent-activity widget."}, {"admin:dashboard:code", "Writing pages, layouts, components, error pages, stylesheets and JS hooks, their code included, and opening every builder."}, {"admin:api:runtime:*", "Every runtime section over the admin JSON:API, their code included."}, {"admin:api:runtime:site", "Site data over the admin JSON:API."}, {"admin:api:runtime:page", "Writing pages over the admin JSON:API, their code included."}, {"admin:api:runtime:layout", "Writing layouts over the admin JSON:API, their code included."}, {"admin:api:runtime:component", "Writing components over the admin JSON:API, their code included."}, {"admin:api:runtime:error_page", "Writing error pages over the admin JSON:API, their code included."}, {"admin:api:runtime:stylesheet", "Writing stylesheets over the admin JSON:API, their CSS included."}, {"admin:api:runtime:js_hook", "Writing JS hooks over the admin JSON:API, their content included."}, {"admin:api:runtime:media", "Writing media and media categories over the admin JSON:API."}, {"admin:api:runtime:translation", "Writing translations over the admin JSON:API."}, {"admin:api:runtime:redirect", "Writing redirect rules over the admin JSON:API."}, {"admin:api:user", "Accounts over the admin JSON:API."}, {"admin:api:permission", "The permission names and the grants over the admin JSON:API."}, {"admin:api:runtime:activity", "The activity log over the admin JSON:API."}, {"admin:api:code", "Writing pages, layouts, components, error pages, stylesheets and JS hooks over the admin JSON:API, their code included."}])

@spec ensure_permissions([{String.t(), String.t()}]) ::
  {:ok,
   %{
     stored: [MishkaCmsCore.Accounts.Permission.t()],
     created: [MishkaCmsCore.Accounts.Permission.t()]
   }}
  | {:error, term()}

Creates each name of permissions (the core's, permissions/0, when none are given) that is not stored yet. Returns every one of them, stored before or now, and the ones this call created. A stored name keeps its row and its description.

permissions()

@spec permissions() :: [{String.t(), String.t()}]

Every permission name the core install creates, with its description. An extension's own names are not here: its provision manifest ships them, and installing it creates them (MishkaCmsCore.Runtime.ExtensionProvision).

run(params, progress \\ fn _ -> :ok end)

@spec run(params(), (term() -> any())) ::
  {:ok, %{user: map(), site: map()}} | {:error, term()}

Runs the install phases in order, calling progress with {step_id, status, detail} as each phase starts (:running), emits a line (:log), finishes (:done) or fails (:error). Returns {:ok, %{user: user, site: site}} or {:error, reason} from the first failing phase. run/1 keeps the silent behaviour for non-interactive callers (seeds, tests).

steps()

@spec steps() :: [{atom(), String.t()}]

The ordered install phases as {id, label} — one progress row per entry.