feat(settings): support dynamic number of SMS template variables

This commit is contained in:
2026-08-15 21:58:10 +03:30
parent 9472537536
commit d2d4ee719b
3 changed files with 168 additions and 66 deletions
+40 -24
View File
@@ -9,7 +9,7 @@ const {
sanitizeVariableName,
emptyTemplateEntry,
normalizeStoredEntry,
resolveVariableMap
resolveVariablesList
} = require('./smsTemplates');
const emptyTemplateMap = () => {
@@ -36,18 +36,25 @@ const resolveTemplateId = (entry, def) => {
const toPublicTemplates = (storedMap) => {
return SMS_TEMPLATE_DEFS.map((def) => {
const entry = normalizeStoredEntry(storedMap[def.key]);
const variables = resolveVariableMap(def, entry.variables);
const entry = normalizeStoredEntry(storedMap[def.key], def);
const variables = resolveVariablesList(def, entry.variables);
return {
key: def.key,
label: def.label,
templateId: resolveTemplateId(entry, def),
variables: (def.slots || []).map((slot) => ({
availableSlots: (def.slots || []).map((slot) => ({
slot: slot.key,
label: slot.label,
defaultName: slot.defaultName,
name: variables[slot.key]
}))
defaultName: slot.defaultName
})),
variables: variables.map((v) => {
const slotDef = (def.slots || []).find((s) => s.key === v.slot);
return {
slot: v.slot,
label: slotDef?.label || v.slot,
name: v.name
};
})
};
});
};
@@ -63,13 +70,13 @@ const getSettings = async () => {
const getSmsTemplate = async (key) => {
const def = SMS_TEMPLATE_DEFS.find((item) => item.key === key);
if (!def) {
return { templateId: '', variables: {} };
return { templateId: '', variables: [] };
}
const doc = await Setting.findOne({ key: SETTINGS_KEY }).lean();
const entry = normalizeStoredEntry(readStoredMap(doc)[key]);
const entry = normalizeStoredEntry(readStoredMap(doc)[key], def);
return {
templateId: resolveTemplateId(entry, def),
variables: resolveVariableMap(def, entry.variables)
variables: resolveVariablesList(def, entry.variables)
};
};
@@ -101,12 +108,12 @@ const saveSettings = async (body = {}) => {
const nextMap = emptyTemplateMap();
for (const def of SMS_TEMPLATE_DEFS) {
const storedEntry = normalizeStoredEntry(storedMap[def.key]);
const storedEntry = normalizeStoredEntry(storedMap[def.key], def);
const hasIncoming = Object.prototype.hasOwnProperty.call(incomingMap, def.key);
const incomingEntry = hasIncoming ? parseIncomingEntry(incomingMap[def.key]) : null;
let templateId = storedEntry.templateId || envFallbackFor(def);
let variables = { ...storedEntry.variables };
let variables = storedEntry.variables;
if (incomingEntry) {
if (incomingEntry.templateId !== undefined) {
@@ -117,23 +124,32 @@ const saveSettings = async (body = {}) => {
templateId = sanitized;
}
if (incomingEntry.variables && typeof incomingEntry.variables === 'object') {
const incomingVars = Array.isArray(incomingEntry.variables)
? Object.fromEntries(incomingEntry.variables.map((item) => [item.slot, item.name]))
: incomingEntry.variables;
if (incomingEntry.variables !== undefined) {
let rawList = [];
if (Array.isArray(incomingEntry.variables)) {
rawList = incomingEntry.variables;
} else if (incomingEntry.variables && typeof incomingEntry.variables === 'object') {
rawList = Object.entries(incomingEntry.variables).map(([slot, name]) => ({ slot, name }));
}
(def.slots || []).forEach((slot) => {
if (!Object.prototype.hasOwnProperty.call(incomingVars, slot.key)) return;
const sanitized = sanitizeVariableName(incomingVars[slot.key]);
const validVars = [];
for (const item of rawList) {
if (!item || typeof item !== 'object') continue;
const slot = String(item.slot || '').trim();
if (!slot) continue;
const rawName = String(item.name || '').trim();
if (!rawName) continue;
const sanitized = sanitizeVariableName(rawName);
if (sanitized === null) {
throw new AppError(
'VALIDATION_FAILED',
{ field: `${def.key}.${slot.key}` },
`Invalid SMS variable name for ${def.key}.${slot.key}`
{ field: `${def.key}.${slot}` },
`Invalid SMS variable name "${rawName}" for ${def.key}`
);
}
variables[slot.key] = sanitized || slot.defaultName;
});
validVars.push({ slot, name: sanitized });
}
variables = validVars;
}
} else if (!storedEntry.templateId) {
templateId = envFallbackFor(def);
@@ -141,7 +157,7 @@ const saveSettings = async (body = {}) => {
nextMap[def.key] = {
templateId,
variables: resolveVariableMap(def, variables)
variables: resolveVariablesList(def, variables)
};
}