'use strict'; const AppError = require('../../utils/AppError'); const { Setting, SETTINGS_KEY } = require('./settingModel'); const { SMS_TEMPLATE_DEFS, envFallbackFor, emptyTemplateEntry, normalizeStoredEntry, resolveVariablesList, mergeTemplateEntry } = 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.toObject ? existing.toObject() : existing) : emptyTemplateMap(); const nextMap = {}; for (const def of SMS_TEMPLATE_DEFS) { const hasIncoming = Object.prototype.hasOwnProperty.call(incomingMap, def.key); const incomingEntry = hasIncoming ? parseIncomingEntry(incomingMap[def.key]) : null; try { nextMap[def.key] = mergeTemplateEntry(def, storedMap[def.key], incomingEntry); } catch (err) { if (err.code === 'INVALID_TEMPLATE_ID') { throw new AppError('VALIDATION_FAILED', { field: def.key }, `شناسه قالب پیامک برای ${def.label} نامعتبر است`); } if (err.code === 'INVALID_VARIABLE_NAME') { throw new AppError( 'VALIDATION_FAILED', { field: err.field || def.key }, `نام متغیر «${err.rawName}» برای ${def.label} نامعتبر است` ); } throw err; } } const doc = existing || new Setting({ key: SETTINGS_KEY }); doc.set('smsTemplates', JSON.parse(JSON.stringify(nextMap))); doc.markModified('smsTemplates'); await doc.save(); const saved = await Setting.findOne({ key: SETTINGS_KEY }).lean(); return { smsTemplates: toPublicTemplates(readStoredMap(saved)) }; }; module.exports = { getSettings, saveSettings, getSmsTemplate, getSmsTemplateId };