PhoenixKit.Modules.Emails.TemplateExport (phoenix_kit_emails v0.5.0)

Copy Markdown View Source

Decides which stored templates become override files, and what those files are called.

Message templates are moving from phoenix_kit_email_templates to files a host owns in its own repository. plan/2 answers the two questions that migration turns on — which rows carry an edit worth keeping, and what filename preserves the behaviour that row had — as data, so mix phoenix_kit_emails.templates.export is left with nothing to do but write and report.

Which rows

RowPlannedWhy
System template, editedyesthe edit is the thing worth keeping
System template, untouchednobyte-identical to what this package ships; core supplies it now, translated
Operator-authored (is_system: false)nonewsletter layouts, authored at runtime — they keep a table and an editor

A template whose name this package does not ship counts as edited: nothing is known about what it should look like, and a file too many is recoverable where a dropped edit is not.

Which filename

The name becomes a directory and each part a file. The subtle part is the locale: a stored field is a language map, and Template.get_translation/3 falls back through it, so one locale's content is what every recipient without their own translation actually receives. That locale must be written without a code in the filename, because a locale-less file is what core's resolution falls through to. Writing it as subject.en.txt would silently drop the operator's customization for every non-English recipient — the exact opposite of the point of exporting it.

Summary

Types

What an export would do, without having done any of it.

Functions

Whether template still matches what this package ships under its name.

The locale every recipient falls through to: "en" when the map has it — the key Template.get_translation/3 itself defaults to — otherwise the lowest-sorting one, so the choice is deterministic rather than whatever the map happens to yield first.

The {path, content} pairs one template exports to.

Plans an export of templates against shipped (this package's own defaults, as default_system_templates/0 returns them).

Writes planned {path, content} pairs, returning {path, outcome} for each.

Types

plan()

@type plan() :: %{
  edited: [PhoenixKit.Modules.Emails.Template.t()],
  untouched: [PhoenixKit.Modules.Emails.Template.t()],
  authored: [PhoenixKit.Modules.Emails.Template.t()],
  files: [{Path.t(), String.t()}]
}

What an export would do, without having done any of it.

Functions

edited?(template, by_name)

@spec edited?(PhoenixKit.Modules.Emails.Template.t(), %{optional(String.t()) => map()}) ::
  boolean()

Whether template still matches what this package ships under its name.

Compares the three content fields only — a slug, a usage count or a timestamp differing does not make a template customized.

fallback_locale(field_map)

@spec fallback_locale(map()) :: String.t() | nil

The locale every recipient falls through to: "en" when the map has it — the key Template.get_translation/3 itself defaults to — otherwise the lowest-sorting one, so the choice is deterministic rather than whatever the map happens to yield first.

files_for(template, out \\ "priv/phoenix_kit_templates")

@spec files_for(PhoenixKit.Modules.Emails.Template.t(), Path.t()) :: [
  {Path.t(), String.t()}
]

The {path, content} pairs one template exports to.

Empty and non-string values are skipped: a part a template does not supply is absent, not an empty file.

plan(templates, shipped, opts \\ [])

@spec plan([PhoenixKit.Modules.Emails.Template.t()], [map()], keyword()) :: plan()

Plans an export of templates against shipped (this package's own defaults, as default_system_templates/0 returns them).

Options: :out, the target directory (default priv/phoenix_kit_templates).

write_files(files, opts \\ [])

@spec write_files(
  [{Path.t(), String.t()}],
  keyword()
) :: [{Path.t(), :written | :would_write | :skipped}]

Writes planned {path, content} pairs, returning {path, outcome} for each.

Outcomes are :written, :would_write (under dry_run: true) and :skipped — a path that already exists is refused unless force: true. That refusal is the point: a re-run, or an export onto a host that has already hand-written an override, must never silently replace a file a human wrote. Nothing here deletes.