feat(whatsapp): admin-selectable template parameters + reorder (#647 follow-up)
Reporter @Rekoo-PS confirmed the language fix unblocked sending, then
hit a second gap: their template uses only `{{1}} = event_name` +
`{{2}} = gallery_link`, but the legacy `buildComponents` hardcoded all
5 positional values from the `gallery_ready` shape (customer_name,
event_name, gallery_link, password_line, expiry_date). Meta rejected
with a parameter-count mismatch even after the language matched.
This adds a per-config slot list — which built-in values to send, and
in what positional order — so admins can match templates of any shape
without code changes.
## Schema (migration 138)
Additive `template_params` TEXT column on `whatsapp_configs` (default
empty string = legacy 5-slot behaviour for existing installs). Stored
as a JSON-serialized array of slot keys: `customer_name`, `event_name`,
`gallery_link`, `password_line`, `expiry_date`. Unknown / duplicate /
non-string entries are sanitized out at read time.
## Processor
- `parseTemplateParams(raw)` — defensive parser; falls back to the
5-slot default on empty / malformed / all-invalid input.
- `buildComponents(data, metaLang, params)` — emits ONLY the listed
slots in the listed order, computed via a small switch on slot key.
The password line still receives the locale-specific 🔒 label and
the empty-when-no-real-password sentinel handling.
- Processor reads `config.template_params` once per cycle and passes
the parsed array to `buildComponents` per message.
## Admin route
- GET surfaces `template_params` as the parsed array (default 5-slot
when null/empty).
- PUT round-trips the incoming array through `parseTemplateParams`
before persisting, so the stored value is always the canonical
sanitized JSON.
- Test send rebuilt to use the same `buildComponents` path so the
admin's test message matches their configured slot shape — a
reporter who configures 2 slots gets a 2-parameter test send, not
the legacy 5-parameter payload.
## UI
- `WhatsAppTab` gets a checkbox + up/down list under the Template
language field. Each slot shows its current `{{N}}` position when
checked, an em-dash when unchecked. Live preview below the list:
"Your template will receive: {{1}} = event_name, {{2}} = gallery_link".
- EN + DE i18n for the field labels, hint, preview, and per-slot
human-readable names.
## Tests
- 17 unit tests in `__tests__/utils/whatsappBuildComponents.test.js`
covering: parseTemplateParams sanitization (unknown keys, duplicates,
non-strings, malformed JSON, all-invalid fallback, pre-parsed array
acceptance) and buildComponents shape (reporter's 2-slot case,
reorder, empty list, locale-specific password label, password
sentinel handling, expiry omission).
- All 17 + the 34 existing networkValidation tests pass.
## Migration numbering
Sits at 138 on top of PR #649's migration 137. If #646 (Live Slideshow)
merges before this, #646's own 137 + 138 take precedence and this
needs renumbering to 139. Coordinated via PR #646's review thread.
## Honest caveat
Still no Meta Business API account on my side. Spec-built, sanitizer +
shape unit-tested, lint + tsc clean. End-to-end against Meta needs the
reporter (or a maintainer with an account) to verify. If a real
round-trip surfaces a mismatch, drop it in #647 and I'll iterate.
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Migration 138: WhatsApp template parameter selection (#647 follow-up).
|
||||
*
|
||||
* Adds a `template_params` column to `whatsapp_configs` that stores an
|
||||
* ordered JSON array of slot keys naming which built-in values are sent
|
||||
* as positional parameters to the configured Meta template (and in what
|
||||
* order). Reporter @Rekoo-PS hit the gap that motivated this: their
|
||||
* template body uses only `{{1}} = event_name` + `{{2}} = gallery_link`,
|
||||
* but the hardcoded `buildComponents` shape always emitted 5 parameters
|
||||
* matching `gallery_ready` — so Meta rejected with a parameter-count
|
||||
* mismatch even after the language fix landed (migration 137).
|
||||
*
|
||||
* Schema: TEXT column, empty/null means "fall back to the legacy 5-slot
|
||||
* shape" so installs that haven't reconfigured continue to work without
|
||||
* intervention. The processor's `buildComponents` reads this column,
|
||||
* parses the array, and emits only the listed slots in the listed order.
|
||||
*
|
||||
* Known slot keys (any other keys are ignored): `customer_name`,
|
||||
* `event_name`, `gallery_link`, `password_line`, `expiry_date`.
|
||||
*
|
||||
* Additive + `hasColumn`-guarded. The settling number depends on the
|
||||
* merge order with PR #646; flagged for renumber if that lands first.
|
||||
*/
|
||||
exports.up = async function (knex) {
|
||||
if (!(await knex.schema.hasTable('whatsapp_configs'))) return;
|
||||
if (await knex.schema.hasColumn('whatsapp_configs', 'template_params')) return;
|
||||
await knex.schema.alterTable('whatsapp_configs', (table) => {
|
||||
table.text('template_params').notNullable().defaultTo('');
|
||||
});
|
||||
};
|
||||
|
||||
exports.down = async function (knex) {
|
||||
if (!(await knex.schema.hasTable('whatsapp_configs'))) return;
|
||||
if (!(await knex.schema.hasColumn('whatsapp_configs', 'template_params'))) return;
|
||||
await knex.schema.alterTable('whatsapp_configs', (table) => {
|
||||
table.dropColumn('template_params');
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user