Store custom sms.ir parameter names per template slot so outbound messages match the panel placeholders.
165 lines
4.8 KiB
JavaScript
165 lines
4.8 KiB
JavaScript
'use strict';
|
|
|
|
const AppError = require('../../utils/AppError');
|
|
const { Setting, SETTINGS_KEY } = require('./settingModel');
|
|
const {
|
|
SMS_TEMPLATE_DEFS,
|
|
envFallbackFor,
|
|
sanitizeTemplateId,
|
|
sanitizeVariableName,
|
|
emptyTemplateEntry,
|
|
normalizeStoredEntry,
|
|
resolveVariableMap
|
|
} = require('./smsTemplates');
|
|
|
|
const emptyTemplateMap = () => {
|
|
const map = {};
|
|
SMS_TEMPLATE_DEFS.forEach((def) => {
|
|
map[def.key] = emptyTemplateEntry(def);
|
|
});
|
|
return map;
|
|
};
|
|
|
|
const readStoredMap = (doc) => {
|
|
const stored = doc?.smsTemplates || {};
|
|
if (stored instanceof Map) {
|
|
return Object.fromEntries(stored.entries());
|
|
}
|
|
return { ...stored };
|
|
};
|
|
|
|
const resolveTemplateId = (entry, def) => {
|
|
const fromDb = String(entry?.templateId || '').trim();
|
|
if (fromDb) return fromDb;
|
|
return envFallbackFor(def);
|
|
};
|
|
|
|
const toPublicTemplates = (storedMap) => {
|
|
return SMS_TEMPLATE_DEFS.map((def) => {
|
|
const entry = normalizeStoredEntry(storedMap[def.key]);
|
|
const variables = resolveVariableMap(def, entry.variables);
|
|
return {
|
|
key: def.key,
|
|
label: def.label,
|
|
templateId: resolveTemplateId(entry, def),
|
|
variables: (def.slots || []).map((slot) => ({
|
|
slot: slot.key,
|
|
label: slot.label,
|
|
defaultName: slot.defaultName,
|
|
name: variables[slot.key]
|
|
}))
|
|
};
|
|
});
|
|
};
|
|
|
|
const getSettings = async () => {
|
|
const doc = await Setting.findOne({ key: SETTINGS_KEY }).lean();
|
|
const storedMap = readStoredMap(doc);
|
|
return {
|
|
smsTemplates: toPublicTemplates(storedMap)
|
|
};
|
|
};
|
|
|
|
const getSmsTemplate = async (key) => {
|
|
const def = SMS_TEMPLATE_DEFS.find((item) => item.key === key);
|
|
if (!def) {
|
|
return { templateId: '', variables: {} };
|
|
}
|
|
const doc = await Setting.findOne({ key: SETTINGS_KEY }).lean();
|
|
const entry = normalizeStoredEntry(readStoredMap(doc)[key]);
|
|
return {
|
|
templateId: resolveTemplateId(entry, def),
|
|
variables: resolveVariableMap(def, entry.variables)
|
|
};
|
|
};
|
|
|
|
const getSmsTemplateId = async (key) => {
|
|
const template = await getSmsTemplate(key);
|
|
return template.templateId;
|
|
};
|
|
|
|
const parseIncomingEntry = (raw) => {
|
|
if (raw == null) return null;
|
|
if (typeof raw === 'string' || typeof raw === 'number') {
|
|
return { templateId: raw, variables: undefined };
|
|
}
|
|
if (typeof raw !== 'object') return null;
|
|
return {
|
|
templateId: raw.templateId,
|
|
variables: raw.variables
|
|
};
|
|
};
|
|
|
|
const saveSettings = async (body = {}) => {
|
|
const incoming = body.smsTemplates || {};
|
|
const incomingMap = Array.isArray(incoming)
|
|
? Object.fromEntries(incoming.map((item) => [item.key, item]))
|
|
: incoming;
|
|
|
|
const existing = await Setting.findOne({ key: SETTINGS_KEY });
|
|
const storedMap = existing ? readStoredMap(existing) : emptyTemplateMap();
|
|
const nextMap = emptyTemplateMap();
|
|
|
|
for (const def of SMS_TEMPLATE_DEFS) {
|
|
const storedEntry = normalizeStoredEntry(storedMap[def.key]);
|
|
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 };
|
|
|
|
if (incomingEntry) {
|
|
if (incomingEntry.templateId !== undefined) {
|
|
const sanitized = sanitizeTemplateId(incomingEntry.templateId);
|
|
if (sanitized === null) {
|
|
throw new AppError('VALIDATION_FAILED', { field: def.key }, `Invalid SMS template ID for ${def.key}`);
|
|
}
|
|
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;
|
|
|
|
(def.slots || []).forEach((slot) => {
|
|
if (!Object.prototype.hasOwnProperty.call(incomingVars, slot.key)) return;
|
|
const sanitized = sanitizeVariableName(incomingVars[slot.key]);
|
|
if (sanitized === null) {
|
|
throw new AppError(
|
|
'VALIDATION_FAILED',
|
|
{ field: `${def.key}.${slot.key}` },
|
|
`Invalid SMS variable name for ${def.key}.${slot.key}`
|
|
);
|
|
}
|
|
variables[slot.key] = sanitized || slot.defaultName;
|
|
});
|
|
}
|
|
} else if (!storedEntry.templateId) {
|
|
templateId = envFallbackFor(def);
|
|
}
|
|
|
|
nextMap[def.key] = {
|
|
templateId,
|
|
variables: resolveVariableMap(def, variables)
|
|
};
|
|
}
|
|
|
|
const doc = await Setting.findOneAndUpdate(
|
|
{ key: SETTINGS_KEY },
|
|
{ $set: { smsTemplates: nextMap } },
|
|
{ upsert: true, new: true, setDefaultsOnInsert: true }
|
|
);
|
|
|
|
return {
|
|
smsTemplates: toPublicTemplates(readStoredMap(doc))
|
|
};
|
|
};
|
|
|
|
module.exports = {
|
|
getSettings,
|
|
saveSettings,
|
|
getSmsTemplate,
|
|
getSmsTemplateId
|
|
};
|