// Integração NewWhats (satélite) para o scoreodonto — ESM, adaptada do mercado. // Registra rotas no app Express existente. // // Uso no server.js: // import { registerNewwhats } from './newwhats/index.js' // registerNewwhats(app, pool, { superadminGuard }) import { createRoutes } from './routes.js' import { createWebhookReceiver } from './webhook-receiver.js' import { createRestProxy } from './proxy.js' import { getConfig, isConfigured, loadConfigFromDb, saveConfigToDb } from './config.js' export { syncKnowledge, buildKnowledgeText } from './sync-knowledge.js' export function registerNewwhats(app, pool, opts = {}) { // passthrough middleware caso o guard não seja fornecido (dev) const superadminGuard = opts.superadminGuard || ((_req, _res, next) => next()); // Carrega a config do banco para o cache (não bloqueia o boot). loadConfigFromDb(pool).then((c) => console.log(`[newwhats] config carregada — ${isConfigured() ? 'configurada' : 'AGUARDANDO config'} (clinica=${c.clinicaId || '—'})`) ).catch(() => {}); // ── Config (super-admin) ─────────────────────────────────────────────────── // GET: nunca devolve os segredos em claro — só se estão definidos. app.get('/api/nw/config', superadminGuard, (_req, res) => { const c = getConfig(); res.json({ motorUrl: c.motorUrl, clinicaId: c.clinicaId, hasIntegrationKey: Boolean(c.integrationKey), hasWebhookSecret: Boolean(c.webhookSecret), configured: isConfigured(), }); }); // PUT: secrets em branco MANTÊM o valor atual (não precisa redigitar). app.put('/api/nw/config', superadminGuard, async (req, res) => { try { const cur = getConfig(); const b = req.body || {}; const next = { motorUrl: (b.motorUrl ?? cur.motorUrl) || '', clinicaId: (b.clinicaId ?? cur.clinicaId) || '', integrationKey: b.integrationKey ? String(b.integrationKey) : cur.integrationKey, webhookSecret: b.webhookSecret ? String(b.webhookSecret) : cur.webhookSecret, }; await saveConfigToDb(pool, next); res.json({ ok: true, configured: isConfigured() }); } catch (e) { res.status(500).json({ error: e.message }); } }); // Lista de clínicas para o dropdown. app.get('/api/nw/clinicas', superadminGuard, async (_req, res) => { try { const { rows } = await pool.query('SELECT id, nome_fantasia FROM clinicas ORDER BY nome_fantasia'); res.json(rows); } catch (e) { res.status(500).json({ error: e.message }); } }); // Testa a conexão com o motor (usa a config atual). app.get('/api/nw/status', superadminGuard, async (_req, res) => { const c = getConfig(); if (!c.motorUrl || !c.integrationKey) return res.json({ configured: false, motorOk: false, detail: 'URL ou chave ausente' }); try { const r = await fetch(`${c.motorUrl}/api/ext/v1/sessions`, { headers: { 'x-nw-key': c.integrationKey } }); res.json({ configured: true, motorOk: r.ok, status: r.status, detail: r.ok ? 'Conexão OK' : `Motor respondeu ${r.status}` }); } catch (e) { res.json({ configured: true, motorOk: false, detail: `Motor inacessível: ${e.message}` }); } }); // ── Operação ──────────────────────────────────────────────────────────────── app.use('/nw', createRoutes(pool)); // Secretária do motor consulta a clínica app.use(createWebhookReceiver(pool)); // webhook motor → satélite app.use('/api/nw/v1', createRestProxy()); // proxy do inbox → motor (express 5: use, não wildcard) console.log('[newwhats] integração registrada (/nw, /api/webhook/newwhats, /api/nw/v1/*, /api/nw/config)'); }