'use strict'; const AppError = require('../../utils/AppError'); const { Setting, SETTINGS_KEY } = require('./settingModel'); const { SMS_TEMPLATE_DEFS, envFallbackFor, sanitizeTemplateId, sanitizeVariableName, emptyTemplateEntry, normalizeStoredEntry, resolveVariablesList } = 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], def); const variables = resolveVariablesList(def, entry.variables); return { key: def.key, label: def.label, templateId: resolveTemplateId(entry, def), availableSlots: (def.slots || []).map((slot) => ({ slot: slot.key, label: slot.label, 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 }; }) }; }); }; 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], def); return { templateId: resolveTemplateId(entry, def), variables: resolveVariablesList(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], 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; 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 !== 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 })); } 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}` }, `Invalid SMS variable name "${rawName}" for ${def.key}` ); } validVars.push({ slot, name: sanitized }); } variables = validVars; } } else if (!storedEntry.templateId) { templateId = envFallbackFor(def); } nextMap[def.key] = { templateId, variables: resolveVariablesList(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 };