'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 { parseIncomingMessaging } = require('../../utils/messagingChannels'); const { getPublicMessaging, invalidateMessagingCache } = require('./messagingFlags'); const { emptyNotificationSettingsMap, mergeNotificationSettings, parseIncomingNotificationSettings, NOTIFICATION_ACTION_KEYS } = require('./notificationActions'); const { getPublicNotificationSettings, invalidateNotificationSettingsCache, readStoredMap: readNotificationStoredMap } = require('../../utils/notificationSettings'); 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 () => { let doc = await Setting.findOne({ key: SETTINGS_KEY }).lean(); if (!doc) { const created = await Setting.create({ key: SETTINGS_KEY, smsTemplates: emptyTemplateMap(), notificationSettings: emptyNotificationSettingsMap() }); doc = created.toObject(); } else { const mergedNotifications = mergeNotificationSettings(readNotificationStoredMap(doc)); const storedKeys = Object.keys(readNotificationStoredMap(doc)); const missingAction = NOTIFICATION_ACTION_KEYS.some((key) => !storedKeys.includes(key)); if (missingAction || storedKeys.length === 0) { await Setting.updateOne( { key: SETTINGS_KEY }, { $set: { notificationSettings: mergedNotifications } } ); doc = { ...doc, notificationSettings: mergedNotifications }; invalidateNotificationSettingsCache(); } } const storedMap = readStoredMap(doc); return { smsTemplates: toPublicTemplates(storedMap), messaging: getPublicMessaging(doc), notificationSettings: await getPublicNotificationSettings(doc) }; }; 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 existing = await Setting.findOne({ key: SETTINGS_KEY }); const doc = existing || new Setting({ key: SETTINGS_KEY }); const hasTemplatePayload = body.smsTemplates !== undefined; if (hasTemplatePayload) { const incoming = body.smsTemplates || {}; const incomingMap = Array.isArray(incoming) ? Object.fromEntries(incoming.map((item) => [item.key, item])) : incoming; 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; } } doc.set('smsTemplates', JSON.parse(JSON.stringify(nextMap))); doc.markModified('smsTemplates'); } else if (!existing) { doc.set('smsTemplates', emptyTemplateMap()); } const incomingMessaging = parseIncomingMessaging(body); if (incomingMessaging) { if (incomingMessaging.smsEnabled !== undefined) doc.smsEnabled = incomingMessaging.smsEnabled; if (incomingMessaging.emailEnabled !== undefined) doc.emailEnabled = incomingMessaging.emailEnabled; if (incomingMessaging.botEnabled !== undefined) doc.botEnabled = incomingMessaging.botEnabled; } const incomingNotificationSettings = parseIncomingNotificationSettings(body.notificationSettings); if (incomingNotificationSettings !== undefined) { const storedNotificationMap = existing ? readNotificationStoredMap(existing.toObject ? existing.toObject() : existing) : emptyNotificationSettingsMap(); const nextNotificationMap = mergeNotificationSettings(storedNotificationMap, incomingNotificationSettings); doc.set('notificationSettings', JSON.parse(JSON.stringify(nextNotificationMap))); doc.markModified('notificationSettings'); } else if (!existing) { doc.set('notificationSettings', emptyNotificationSettingsMap()); } await doc.save(); invalidateMessagingCache(); invalidateNotificationSettingsCache(); const saved = await Setting.findOne({ key: SETTINGS_KEY }).lean(); return { smsTemplates: toPublicTemplates(readStoredMap(saved)), messaging: getPublicMessaging(saved), notificationSettings: await getPublicNotificationSettings(saved) }; }; module.exports = { getSettings, saveSettings, getSmsTemplate, getSmsTemplateId };