fix(email): repair and seed the gallery lifecycle templates
Correction: the reported premise held for only one of the three templates,
verified by running the core migration set against an empty database.
- expiration_warning is German-is-English on every fresh install, exactly as
reported. Repaired with migration 194's pattern verbatim.
- gallery_expired and archive_complete are NOT German-is-English -- they do
not exist at all. Their master rows are inserted only by migrations/legacy/
010+020, which never run on a fresh install, so 075/099/106/108 seeded zero
translations for them (they key off a master row that is not there). A
fresh install's email_templates holds 17 keys and neither is among them.
The consequence is worse than a translation gap: expirationChecker's
sendGalleryExpiredEmails and archiveService's completion mail both hit
"Email template not found", retry three times and die silently in
email_queue on every expiry and every archive.
So 195 also seeds those two (master row + en/de translations + category),
but only when the master row is absent -- it never overwrites. English
follows legacy 028, which emailProcessor's own comments call the shipped
copy; German follows legacy 026's wording. Both are restructured into the
plain unstyled shape the other core-seeded templates use, so wrapEmailHtml's
configurable palette governs styling rather than hard-coded hex. The
support-contact line is wrapped in {{#if support_email}} because
getSupportEmail() can return ''.
196 adds the {{#if welcome_message}} block that nl/pt/ru/fr/es/sl already
have in gallery_created but en and de lack, so the photographer's personal
note was silently dropped for those two locales even though the value is
passed at send time. safeTemplateReplace does resolve {{#if}} before variable
substitution, so this is a real conditional -- there is a test rendering the
migrated body both ways. HTML body only, matching the other locales:
emailProcessor rewrites welcome_message through formatWelcomeMessage
(escape + nl2br) once for both bodies, so the text part would print literal
<br /> and &.
Both migrations keep 194's conservative condition -- rewrite only while the
German is still byte-identical to English or empty -- so admin-edited and
legacy-translated installs are untouched. Idempotent, guarded, no-op down().
Known gap, documented in 195's header: the two newly seeded templates get
en/de only. nl/pt/ru/fr/es/sl fall back to en via processTemplate's fallback
chain, which is strictly better than today's hard failure but is not real
localisation.
Refs testplan REPORT.md B1, B2.
This commit is contained in:
@@ -0,0 +1,282 @@
|
||||
/**
|
||||
* Migration 195: finish the job migration 194 started for the remaining three
|
||||
* customer-facing gallery-lifecycle mails — `expiration_warning`,
|
||||
* `gallery_expired` and `archive_complete`.
|
||||
*
|
||||
* Investigating them turned up TWO different fresh-install defects, not one:
|
||||
*
|
||||
* 1. `expiration_warning` is the exact defect 194 fixed for
|
||||
* `gallery_created`: migration 001 seeds it, 059 copies `_en` into `_de`
|
||||
* ("Copy to German as default"), 075 materialises those columns as the
|
||||
* `de` translation row, and the real German only ever existed in the
|
||||
* LEGACY migrations (026), which never run on a fresh install. Every
|
||||
* install created since then mails English to German-locale recipients
|
||||
* while nl/pt/ru/fr/es/sl are all localised.
|
||||
*
|
||||
* 2. `gallery_expired` and `archive_complete` are NOT German-is-English on a
|
||||
* fresh install — they are ABSENT. Their master rows are only ever
|
||||
* inserted by legacy migrations 010/020; no core migration seeds them.
|
||||
* Verified by running the core migration set against an empty database:
|
||||
* the resulting `email_templates` holds 17 keys and neither of these two
|
||||
* is among them. So `expirationChecker.sendGalleryExpiredEmails` and
|
||||
* `archiveService`'s completion mail both hit
|
||||
* `Email template '<key>' not found` in emailProcessor (line 759) on
|
||||
* every fresh install — the queue row then retries three times and dies
|
||||
* silently. 075/099/106/108 seeded no translations for them either,
|
||||
* because they key off a master row that does not exist.
|
||||
*
|
||||
* This migration therefore does two things, both conservative:
|
||||
*
|
||||
* - REPAIR: rewrite a German row (and the legacy `_de` columns) only while
|
||||
* it is still byte-identical to the English one, or empty. That is
|
||||
* precisely the broken state. A legacy install whose German came from
|
||||
* migration 026, or any install where an admin edited the template, is
|
||||
* left untouched.
|
||||
* - SEED: insert `gallery_expired` / `archive_complete` with EN + DE only
|
||||
* when the master row is missing entirely. Never overwrites an existing
|
||||
* row.
|
||||
*
|
||||
* The German copy follows legacy migration 026's wording — the translation
|
||||
* that was always intended — restructured to mirror the English it sits next
|
||||
* to, so the placeholder set of each German body matches its English original
|
||||
* exactly. The seeded English follows legacy 028 (which emailProcessor's own
|
||||
* comments treat as the shipped copy) but in the plain, unstyled shape the
|
||||
* other core-seeded templates use, so `wrapEmailHtml`'s configurable email
|
||||
* palette governs the styling instead of hard-coded hex values.
|
||||
*
|
||||
* Known gap, deliberately not closed here: the two seeded templates get en/de
|
||||
* only. nl/pt/ru/fr/es/sl fall back to `en` via processTemplate's fallback
|
||||
* chain, which is strictly better than today's hard failure.
|
||||
*/
|
||||
|
||||
// ── expiration_warning ────────────────────────────────────────────────
|
||||
// Placeholders mirror the English original seeded by migration 001:
|
||||
// host_name, event_name, days_remaining, gallery_link.
|
||||
|
||||
const WARNING_SUBJECT_DE = 'Ihre Fotogalerie läuft bald ab';
|
||||
|
||||
const WARNING_HTML_DE = `<h2>Galerie läuft bald ab</h2>
|
||||
<p>Guten Tag {{host_name}},</p>
|
||||
<p>Ihre Fotogalerie „{{event_name}}“ läuft in {{days_remaining}} Tagen ab.</p>
|
||||
<p>Nach Ablauf wird die Galerie archiviert und ist für Gäste nicht mehr zugänglich.</p>
|
||||
<p><a href="{{gallery_link}}">Galerie ansehen</a></p>`;
|
||||
|
||||
const WARNING_TEXT_DE = 'Galerie läuft bald ab\n\nGuten Tag {{host_name}},\n\nIhre Fotogalerie „{{event_name}}“ läuft in {{days_remaining}} Tagen ab.';
|
||||
|
||||
// ── gallery_expired ───────────────────────────────────────────────────
|
||||
// Variables filled by expirationChecker.sendGalleryExpiredEmails.
|
||||
// {{support_email}} is wrapped in a conditional because getSupportEmail()
|
||||
// returns '' when neither branding_support_email nor an SMTP from-address
|
||||
// is configured (see emailProcessor.js:110).
|
||||
|
||||
const EXPIRED_VARIABLES = ['host_name', 'event_name', 'event_date', 'expiry_date', 'support_email'];
|
||||
|
||||
const EXPIRED_EN = {
|
||||
subject: 'Your Photo Gallery Has Expired',
|
||||
body_html: `<h2>Gallery Expired</h2>
|
||||
<p>Dear {{host_name}},</p>
|
||||
<p>Your photo gallery "{{event_name}}" expired on {{expiry_date}} and is no longer accessible online.</p>
|
||||
<p>Your photos have been archived safely — nothing is lost.</p>
|
||||
<p><strong>Gallery Details:</strong></p>
|
||||
<ul>
|
||||
<li>Event Date: {{event_date}}</li>
|
||||
<li>Expired: {{expiry_date}}</li>
|
||||
</ul>
|
||||
{{#if support_email}}<p>If you need access to the archived photos, contact us at <a href="mailto:{{support_email}}">{{support_email}}</a>.</p>{{/if}}`,
|
||||
body_text: 'Gallery Expired\n\nDear {{host_name}},\n\nYour photo gallery "{{event_name}}" expired on {{expiry_date}} and is no longer accessible online.\n\nYour photos have been archived safely — nothing is lost.\n\nEvent Date: {{event_date}}\nExpired: {{expiry_date}}\n\n{{#if support_email}}If you need access to the archived photos, contact us at {{support_email}}.{{/if}}',
|
||||
};
|
||||
|
||||
const EXPIRED_DE = {
|
||||
subject: 'Ihre Fotogalerie ist abgelaufen',
|
||||
body_html: `<h2>Galerie abgelaufen</h2>
|
||||
<p>Guten Tag {{host_name}},</p>
|
||||
<p>Ihre Fotogalerie „{{event_name}}“ ist am {{expiry_date}} abgelaufen und online nicht mehr zugänglich.</p>
|
||||
<p>Ihre Fotos wurden sicher archiviert — es geht nichts verloren.</p>
|
||||
<p><strong>Details zur Galerie:</strong></p>
|
||||
<ul>
|
||||
<li>Veranstaltungsdatum: {{event_date}}</li>
|
||||
<li>Abgelaufen am: {{expiry_date}}</li>
|
||||
</ul>
|
||||
{{#if support_email}}<p>Wenn Sie Zugriff auf die archivierten Fotos benötigen, wenden Sie sich an <a href="mailto:{{support_email}}">{{support_email}}</a>.</p>{{/if}}`,
|
||||
body_text: 'Galerie abgelaufen\n\nGuten Tag {{host_name}},\n\nIhre Fotogalerie „{{event_name}}“ ist am {{expiry_date}} abgelaufen und online nicht mehr zugänglich.\n\nIhre Fotos wurden sicher archiviert — es geht nichts verloren.\n\nVeranstaltungsdatum: {{event_date}}\nAbgelaufen am: {{expiry_date}}\n\n{{#if support_email}}Wenn Sie Zugriff auf die archivierten Fotos benötigen, wenden Sie sich an {{support_email}}.{{/if}}',
|
||||
};
|
||||
|
||||
// ── archive_complete ──────────────────────────────────────────────────
|
||||
// Variables filled by archiveService. This one goes to the admin address.
|
||||
|
||||
const ARCHIVE_VARIABLES = ['host_name', 'event_name', 'event_date', 'archive_date', 'photo_count', 'archive_size', 'support_email'];
|
||||
|
||||
const ARCHIVE_EN = {
|
||||
subject: 'Archive Complete: {{event_name}}',
|
||||
body_html: `<h2>Archive Complete</h2>
|
||||
<p>Dear {{host_name}},</p>
|
||||
<p>The photo gallery "{{event_name}}" has been archived successfully.</p>
|
||||
<p><strong>Archive Details:</strong></p>
|
||||
<ul>
|
||||
<li>Event Date: {{event_date}}</li>
|
||||
<li>Archived: {{archive_date}}</li>
|
||||
<li>Number of Photos: {{photo_count}}</li>
|
||||
<li>Archive Size: {{archive_size}}</li>
|
||||
</ul>
|
||||
<p>The archive is stored securely and can be restored if needed.</p>
|
||||
{{#if support_email}}<p>Questions? Contact us at <a href="mailto:{{support_email}}">{{support_email}}</a>.</p>{{/if}}`,
|
||||
body_text: 'Archive Complete\n\nDear {{host_name}},\n\nThe photo gallery "{{event_name}}" has been archived successfully.\n\nEvent Date: {{event_date}}\nArchived: {{archive_date}}\nNumber of Photos: {{photo_count}}\nArchive Size: {{archive_size}}\n\nThe archive is stored securely and can be restored if needed.\n\n{{#if support_email}}Questions? Contact us at {{support_email}}.{{/if}}',
|
||||
};
|
||||
|
||||
const ARCHIVE_DE = {
|
||||
subject: 'Archivierung abgeschlossen: {{event_name}}',
|
||||
body_html: `<h2>Archivierung abgeschlossen</h2>
|
||||
<p>Guten Tag {{host_name}},</p>
|
||||
<p>Die Fotogalerie „{{event_name}}“ wurde erfolgreich archiviert.</p>
|
||||
<p><strong>Details zum Archiv:</strong></p>
|
||||
<ul>
|
||||
<li>Veranstaltungsdatum: {{event_date}}</li>
|
||||
<li>Archiviert am: {{archive_date}}</li>
|
||||
<li>Anzahl der Fotos: {{photo_count}}</li>
|
||||
<li>Archivgröße: {{archive_size}}</li>
|
||||
</ul>
|
||||
<p>Das Archiv wird sicher aufbewahrt und kann bei Bedarf wiederhergestellt werden.</p>
|
||||
{{#if support_email}}<p>Fragen? Wenden Sie sich an <a href="mailto:{{support_email}}">{{support_email}}</a>.</p>{{/if}}`,
|
||||
body_text: 'Archivierung abgeschlossen\n\nGuten Tag {{host_name}},\n\nDie Fotogalerie „{{event_name}}“ wurde erfolgreich archiviert.\n\nVeranstaltungsdatum: {{event_date}}\nArchiviert am: {{archive_date}}\nAnzahl der Fotos: {{photo_count}}\nArchivgröße: {{archive_size}}\n\nDas Archiv wird sicher aufbewahrt und kann bei Bedarf wiederhergestellt werden.\n\n{{#if support_email}}Fragen? Wenden Sie sich an {{support_email}}.{{/if}}',
|
||||
};
|
||||
|
||||
// "Not translated yet" = empty, or still the English text.
|
||||
const isUntranslated = (german, english) => {
|
||||
const de = (german || '').trim();
|
||||
if (!de) return true;
|
||||
return de === (english || '').trim();
|
||||
};
|
||||
|
||||
/**
|
||||
* Rewrite the German translation row + legacy `_de` columns of an existing
|
||||
* template, but only while they are still the English copy (or empty).
|
||||
*/
|
||||
async function repairGerman(knex, templateKey, german, now) {
|
||||
const master = await knex('email_templates').where('template_key', templateKey).first();
|
||||
if (!master) return false;
|
||||
|
||||
if (await knex.schema.hasTable('email_template_translations')) {
|
||||
const enRow = await knex('email_template_translations')
|
||||
.where({ template_id: master.id, language: 'en' })
|
||||
.first();
|
||||
const deRow = await knex('email_template_translations')
|
||||
.where({ template_id: master.id, language: 'de' })
|
||||
.first();
|
||||
|
||||
const englishHtml = (enRow && enRow.body_html) || master.body_html_en || master.body_html || '';
|
||||
|
||||
if (!deRow) {
|
||||
await knex('email_template_translations').insert({
|
||||
template_id: master.id,
|
||||
language: 'de',
|
||||
subject: german.subject,
|
||||
body_html: german.body_html,
|
||||
body_text: german.body_text,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
});
|
||||
} else if (isUntranslated(deRow.body_html, englishHtml)) {
|
||||
await knex('email_template_translations')
|
||||
.where({ id: deRow.id })
|
||||
.update({
|
||||
subject: german.subject,
|
||||
body_html: german.body_html,
|
||||
body_text: german.body_text,
|
||||
updated_at: now,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy per-language columns on the master row — still the fallback path in
|
||||
// emailProcessor.processTemplate when the translations table is unavailable.
|
||||
const cols = await knex('email_templates').columnInfo();
|
||||
if (cols.body_html_de && isUntranslated(master.body_html_de, master.body_html_en)) {
|
||||
await knex('email_templates')
|
||||
.where({ id: master.id })
|
||||
.update({
|
||||
subject_de: german.subject,
|
||||
body_html_de: german.body_html,
|
||||
body_text_de: german.body_text,
|
||||
updated_at: now,
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a master row + en/de translations for a template that is missing
|
||||
* entirely. No-op when the row already exists — this never overwrites.
|
||||
*/
|
||||
async function seedTemplate(knex, templateKey, { en, de, variables }, now) {
|
||||
const existing = await knex('email_templates').where('template_key', templateKey).first();
|
||||
if (existing) return false;
|
||||
|
||||
const cols = await knex('email_templates').columnInfo();
|
||||
const masterRow = { template_key: templateKey, variables: JSON.stringify(variables) };
|
||||
if ('category' in cols) masterRow.category = 'core';
|
||||
if ('subcategory' in cols) masterRow.subcategory = 'gallery';
|
||||
if ('feature_flag' in cols) masterRow.feature_flag = null;
|
||||
if ('created_at' in cols) masterRow.created_at = now;
|
||||
if ('updated_at' in cols) masterRow.updated_at = now;
|
||||
|
||||
// Fill whichever of the legacy subject_*/body_html_*/body_text_* columns the
|
||||
// schema still carries: German into `_de`, English everywhere else.
|
||||
for (const colName of Object.keys(cols)) {
|
||||
const source = /_de$/.test(colName) ? de : en;
|
||||
if (colName === 'subject' || /^subject_[a-z]{2,3}$/i.test(colName)) {
|
||||
masterRow[colName] = source.subject;
|
||||
} else if (colName === 'body_html' || /^body_html_[a-z]{2,3}$/i.test(colName)) {
|
||||
masterRow[colName] = source.body_html;
|
||||
} else if (colName === 'body_text' || /^body_text_[a-z]{2,3}$/i.test(colName)) {
|
||||
masterRow[colName] = source.body_text;
|
||||
}
|
||||
}
|
||||
|
||||
const inserted = await knex('email_templates').insert(masterRow).returning('id');
|
||||
const templateId = typeof inserted[0] === 'object' ? inserted[0].id : inserted[0];
|
||||
|
||||
if (templateId && await knex.schema.hasTable('email_template_translations')) {
|
||||
for (const [language, content] of [['en', en], ['de', de]]) {
|
||||
await knex('email_template_translations').insert({
|
||||
template_id: templateId,
|
||||
language,
|
||||
subject: content.subject,
|
||||
body_html: content.body_html,
|
||||
body_text: content.body_text,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
});
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
exports.up = async function(knex) {
|
||||
if (!(await knex.schema.hasTable('email_templates'))) return;
|
||||
|
||||
const now = new Date().toISOString();
|
||||
|
||||
await repairGerman(knex, 'expiration_warning', {
|
||||
subject: WARNING_SUBJECT_DE,
|
||||
body_html: WARNING_HTML_DE,
|
||||
body_text: WARNING_TEXT_DE,
|
||||
}, now);
|
||||
|
||||
for (const [key, content] of [
|
||||
['gallery_expired', { en: EXPIRED_EN, de: EXPIRED_DE, variables: EXPIRED_VARIABLES }],
|
||||
['archive_complete', { en: ARCHIVE_EN, de: ARCHIVE_DE, variables: ARCHIVE_VARIABLES }],
|
||||
]) {
|
||||
const seeded = await seedTemplate(knex, key, content, now);
|
||||
if (!seeded) {
|
||||
// Row already there (a legacy install). Repair its German the same
|
||||
// conservative way — a no-op wherever legacy 026 already translated it.
|
||||
await repairGerman(knex, key, content.de, now);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.down = async function() {
|
||||
// No-op: reverting would restore English-as-German and delete templates the
|
||||
// expiry/archive mail paths depend on. Admins who want different copy can
|
||||
// edit it under Settings → Email → Templates.
|
||||
};
|
||||
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* Migration 196: give the English (and German) `gallery_created` template the
|
||||
* `{{#if welcome_message}}` block every other locale already has.
|
||||
*
|
||||
* `adminEvents/crud.js:139` puts `welcome_message` on the queued mail payload
|
||||
* for every published gallery, and emailProcessor runs it through
|
||||
* `formatWelcomeMessage` (escape + nl2br) and treats it as HTML-passthrough.
|
||||
* nl/pt/ru/fr/es/sl all render it, via the identical one-liner appended to the
|
||||
* end of the HTML body (migrations 075, 099, 106, 108):
|
||||
*
|
||||
* {{#if welcome_message}}<p><em>{{welcome_message}}</em></p>{{/if}}
|
||||
*
|
||||
* The English copy seeded by migration 001 has no such block, and migration
|
||||
* 194's German was written against that English — so on a fresh install EN and
|
||||
* DE recipients silently lose the photographer's personal note to their client.
|
||||
* (Legacy installs are fine: legacy 028/026 both carry a welcome_message block.)
|
||||
*
|
||||
* `{{#if}}` is a real construct, not wishful markup: `safeTemplateReplace`
|
||||
* (emailProcessor.js:515) resolves `{{#if var}}…{{/if}}` before variable
|
||||
* substitution and drops the block when the variable is empty — so an event
|
||||
* with no welcome message renders exactly as it does today.
|
||||
*
|
||||
* HTML body only, like the other six locales — and deliberately so:
|
||||
* emailProcessor rewrites `welcome_message` through `formatWelcomeMessage`
|
||||
* (HTML-escape + nl2br) once, for both bodies, so dropping it into the plain
|
||||
* text part would print literal `<br />` and `&`.
|
||||
*
|
||||
* Conservative in the same way as 194/195: each body is rewritten only while
|
||||
* it is still byte-identical to the copy this codebase seeded, so an
|
||||
* admin-edited template is never clobbered. The declared `variables` array
|
||||
* gains `welcome_message` unconditionally-if-absent — it is not admin-editable
|
||||
* for seeded templates, and the six locales that already reference the
|
||||
* variable need it declared for the Templates preview to substitute it.
|
||||
*/
|
||||
|
||||
const WELCOME_BLOCK = '\n{{#if welcome_message}}<p><em>{{welcome_message}}</em></p>{{/if}}';
|
||||
|
||||
// The English body seeded by migration 001, verbatim.
|
||||
const SEEDED_HTML_EN = `<h2>Gallery Created Successfully</h2>
|
||||
<p>Dear {{host_name}},</p>
|
||||
<p>Your photo gallery "{{event_name}}" has been created successfully!</p>
|
||||
<p><strong>Gallery Details:</strong></p>
|
||||
<ul>
|
||||
<li>Event Date: {{event_date}}</li>
|
||||
<li>Gallery Link: {{gallery_link}}</li>
|
||||
<li>Password: {{gallery_password}}</li>
|
||||
<li>Expires: {{expiry_date}}</li>
|
||||
</ul>
|
||||
<p>Share this link and password with your guests to allow them to view and download photos.</p>`;
|
||||
|
||||
// The German body written by migration 194, verbatim.
|
||||
const SEEDED_HTML_DE = `<h2>Galerie erfolgreich erstellt</h2>
|
||||
<p>Guten Tag {{host_name}},</p>
|
||||
<p>Ihre Fotogalerie „{{event_name}}“ wurde erfolgreich erstellt!</p>
|
||||
<p><strong>Details zur Galerie:</strong></p>
|
||||
<ul>
|
||||
<li>Veranstaltungsdatum: {{event_date}}</li>
|
||||
<li>Link zur Galerie: <a href="{{gallery_link}}">{{gallery_link}}</a></li>
|
||||
<li>Passwort: {{gallery_password}}</li>
|
||||
<li>Verfügbar bis: {{expiry_date}}</li>
|
||||
</ul>
|
||||
<p>Teilen Sie diesen Link und das Passwort mit Ihren Gästen, damit diese die Fotos ansehen und herunterladen können.</p>`;
|
||||
|
||||
const SEEDED = { en: SEEDED_HTML_EN, de: SEEDED_HTML_DE };
|
||||
|
||||
exports.up = async function(knex) {
|
||||
if (!(await knex.schema.hasTable('email_templates'))) return;
|
||||
|
||||
const master = await knex('email_templates')
|
||||
.where('template_key', 'gallery_created')
|
||||
.first();
|
||||
if (!master) return;
|
||||
|
||||
const now = new Date().toISOString();
|
||||
|
||||
if (await knex.schema.hasTable('email_template_translations')) {
|
||||
for (const [language, seededHtml] of Object.entries(SEEDED)) {
|
||||
const row = await knex('email_template_translations')
|
||||
.where({ template_id: master.id, language })
|
||||
.first();
|
||||
if (!row || (row.body_html || '') !== seededHtml) continue;
|
||||
await knex('email_template_translations')
|
||||
.where({ id: row.id })
|
||||
.update({ body_html: seededHtml + WELCOME_BLOCK, updated_at: now });
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy per-language columns — emailProcessor's fallback path.
|
||||
const cols = await knex('email_templates').columnInfo();
|
||||
const columnUpdate = {};
|
||||
if (cols.body_html_en && (master.body_html_en || '') === SEEDED_HTML_EN) {
|
||||
columnUpdate.body_html_en = SEEDED_HTML_EN + WELCOME_BLOCK;
|
||||
}
|
||||
if (cols.body_html_de && (master.body_html_de || '') === SEEDED_HTML_DE) {
|
||||
columnUpdate.body_html_de = SEEDED_HTML_DE + WELCOME_BLOCK;
|
||||
}
|
||||
if (Object.keys(columnUpdate).length > 0) {
|
||||
columnUpdate.updated_at = now;
|
||||
await knex('email_templates').where({ id: master.id }).update(columnUpdate);
|
||||
}
|
||||
|
||||
// Declare the variable the template (and six other locales) now reference.
|
||||
let variables = master.variables;
|
||||
if (typeof variables === 'string') {
|
||||
try { variables = JSON.parse(variables); } catch (e) { variables = null; }
|
||||
}
|
||||
if (Array.isArray(variables) && !variables.includes('welcome_message')) {
|
||||
await knex('email_templates')
|
||||
.where({ id: master.id })
|
||||
.update({ variables: JSON.stringify([...variables, 'welcome_message']), updated_at: now });
|
||||
}
|
||||
};
|
||||
|
||||
exports.down = async function() {
|
||||
// No-op: reverting would drop the photographer's personal note from the mail
|
||||
// again. Admins who don't want it can remove the block under
|
||||
// Settings → Email → Templates.
|
||||
};
|
||||
Reference in New Issue
Block a user