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

Copy Markdown View Source

The language fields a page written before them has to be given, before anything reads them.

Why

A page's language_group_id is its master's id, and a new page gets its own id in the same write (MishkaCmsCore.Runtime.Resources.Changes.SetLanguageGroupId). A page stored before the column existed has none. Such a page is a master with nothing to join: the sibling relationships join on the group, so a translation that joined its id would be listed under no chip and give it no hreflang link. MishkaCmsCore.Runtime.PageEditions.translate/4 gives a page the boot has not grouped its group with run/1, in the write that creates its first translation.

Migrations are generated per database with mix ash.codegen and are not in the repository, so a data step written into one reaches only the database it was written on. This runs from MishkaCmsCore.Runtime.Handlers.BootHandler instead, before any page is compiled — a compiled page carries its group — on every database that boots this code.

Page's unique_language_per_group identity treats a missing group as distinct for the same reason: the migration that creates its index runs before this does, on a database where every old page has none.

Archived pages

Left as they are. The prevent_archived_page_updates trigger refuses every write to an archived page but the one that restores it, and one statement is all or nothing: a single archived page in it failed the whole UPDATE, and no page on any site got a group. An archived page gets its group from that restore instead — :unarchive and :master_unarchive run SetLanguageGroupId in the same write — and until then it is served nowhere and offered no translation.

Running it

One UPDATE of the live rows with no group, and nothing else — so a database with none left, which is every database after its first boot, is a no-op. SQL rather than an Ash update: the group is derived from the row's own id, and an update per page would be a version, a broadcast and a compile job for each page on the site.

MishkaCmsCore.Runtime.PageLanguageBackfill.run()
#=> {:ok, 12}

Summary

Functions

Marks every live extension-provisioned page as answering all languages, and says how many it changed.

Gives each live page with no language group its own id as one. Returns how many it changed.

run/0 for the one page page_id — 0 when it already has a group or is archived. It runs in the caller's transaction, if there is one.

Functions

mark_extension_pages()

@spec mark_extension_pages() :: {:ok, non_neg_integer()} | {:error, term()}

Marks every live extension-provisioned page as answering all languages, and says how many it changed.

A /:lang/... page answers only the language it was written in — see MishkaCmsCore.Runtime.States.WebRunTimeMetaDataState's edition?/2, which is what stops /fa/about serving the English page to a Persian reader. An extension's pages are the exception, and not because they are unfinished: /:lang/blog holds no prose. It holds components that read the captured language and fetch that language's rows, so one page has always answered every language its site publishes. MishkaCmsCore.Runtime.ExtensionProvision marks them as it writes them; every site provisioned before it learned to would otherwise lose /fa/blog on upgrade.

THE EXTENSION'S OWN PAGES AND NO OTHERS. Core stamps its starter content the same way — home, and in a dev database about, contact and privacy — and those are prose, in one language, and precisely the pages the rule exists for: marking them would put the bug back. A page nobody stamped was written by hand, in a language, and keeps answering that one.

Safe at every boot: a row already carrying the mark is skipped, so the second run changes nothing.

run()

@spec run() :: {:ok, non_neg_integer()} | {:error, term()}

Gives each live page with no language group its own id as one. Returns how many it changed.

run(page_id)

@spec run(Ecto.UUID.t()) :: {:ok, 0 | 1} | {:error, term()}

run/0 for the one page page_id — 0 when it already has a group or is archived. It runs in the caller's transaction, if there is one.