Files
gameno-api/components/settings/settingService.js
T

184 lines
5.5 KiB
JavaScript

'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 }).lean();
const storedMap = existing ? readStoredMap(existing) : emptyTemplateMap();
const nextMap = {};
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 }, `شناسه قالب پیامک برای ${def.label} نامعتبر است`);
}
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();
const sanitized = sanitizeVariableName(rawName);
if (sanitized === null) {
throw new AppError(
'VALIDATION_FAILED',
{ field: `${def.key}.${slot}` },
`نام متغیر «${rawName}» برای ${def.label} نامعتبر است`
);
}
const slotDef = (def?.slots || []).find((s) => s.key === slot);
validVars.push({
slot,
name: sanitized || slotDef?.defaultName || slot
});
}
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 }
).lean();
return {
smsTemplates: toPublicTemplates(readStoredMap(doc))
};
};
module.exports = {
getSettings,
saveSettings,
getSmsTemplate,
getSmsTemplateId
};