'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 };