const WhatsAppTemplate = require('../models/WhatsAppTemplate'); const { getOrgWhatsAppCredentials, listMessageTemplates } = require('../utils/metaWhatsApp'); function normLang(s) { return String(s || '') .trim() .replace(/-/g, '_') .toLowerCase(); } function rejectionFromMeta(m) { if (!m || typeof m !== 'object') return ''; return String(m.rejected_reason || m.reason || '').trim(); } function normalizeCategory(raw) { const c = String(raw || '') .trim() .toUpperCase(); if (c === 'MARKETING' || c === 'UTILITY' || c === 'AUTHENTICATION') return c; return 'UTILITY'; } async function resolveCreatedBy(organizationId, locals) { for (const loc of locals || []) { if (loc.createdBy) return loc.createdBy; } try { const User = require('../models/User'); const u = await User.findOne({ organization: organizationId }).select('_id').lean(); if (u?._id) return u._id; } catch { /* ignore */ } return undefined; } /** * Pull template rows from Meta and upsert local docs (by metaTemplateId or name+language). * Creates local rows for Meta templates that are not yet in the dashboard. * @param {import('mongoose').Types.ObjectId|string} organizationId * @param {{ onlyNames?: string[] }} [opts] — when set, only upsert these Meta template names */ async function syncOrganizationTemplates(organizationId, opts = {}) { const creds = await getOrgWhatsAppCredentials(organizationId); if (!creds) { return { ok: false, error: 'WhatsApp Cloud is not configured for this organisation (WABA + access token).' }; } const data = await listMessageTemplates(creds.wabaId, creds.accessToken); let list = Array.isArray(data.data) ? data.data : []; const onlyNames = Array.isArray(opts.onlyNames) ? opts.onlyNames.map((n) => String(n || '').trim().toLowerCase()).filter(Boolean) : null; if (onlyNames && onlyNames.length) { list = list.filter((m) => onlyNames.includes(String(m.name || '').trim().toLowerCase())); } const locals = await WhatsAppTemplate.find({ organization: organizationId }).lean(); const now = new Date(); let updated = 0; let created = 0; for (const loc of locals) { let m = null; if (loc.metaTemplateId) { m = list.find((x) => String(x.id) === String(loc.metaTemplateId)); } if (!m) { m = list.find((x) => x.name === loc.name && normLang(x.language) === normLang(loc.language)); } if (m) { const st = m.status != null ? String(m.status).toUpperCase() : loc.status; const $set = { metaTemplateId: String(m.id), status: st, rejectionReason: rejectionFromMeta(m), lastSyncedAt: now, lastError: '', }; if (m.category) $set.category = normalizeCategory(m.category); if (Array.isArray(m.components) && m.components.length) $set.components = m.components; await WhatsAppTemplate.updateOne({ _id: loc._id }, { $set }); updated += 1; } } const createdBy = await resolveCreatedBy(organizationId, locals); for (const m of list) { const name = String(m.name || '').trim(); const language = String(m.language || '').trim().replace(/-/g, '_'); if (!name || !language) continue; const exists = locals.some( (loc) => (loc.metaTemplateId && String(loc.metaTemplateId) === String(m.id)) || (loc.name === name && normLang(loc.language) === normLang(language)), ); if (exists) continue; try { await WhatsAppTemplate.create({ organization: organizationId, name, language, category: normalizeCategory(m.category), components: Array.isArray(m.components) ? m.components : [], metaTemplateId: m.id != null ? String(m.id) : '', status: m.status != null ? String(m.status).toUpperCase() : 'PENDING', rejectionReason: rejectionFromMeta(m), lastSyncedAt: now, lastError: '', variableBindings: { header: [], body: [], url: [] }, ...(createdBy ? { createdBy } : {}), }); created += 1; } catch (e) { if (e && e.code === 11000) { await WhatsAppTemplate.updateOne( { organization: organizationId, name, language }, { $set: { metaTemplateId: m.id != null ? String(m.id) : '', status: m.status != null ? String(m.status).toUpperCase() : 'PENDING', rejectionReason: rejectionFromMeta(m), lastSyncedAt: now, lastError: '', ...(m.category ? { category: normalizeCategory(m.category) } : {}), ...(Array.isArray(m.components) && m.components.length ? { components: m.components } : {}), }, }, ); updated += 1; continue; } throw e; } } return { ok: true, metaCount: list.length, updated, created }; } module.exports = { syncOrganizationTemplates };