abbd9374b4
Backend:
- config.js: config lida da tabela settings (id=newwhats_config) com fallback ENV;
loadConfigFromDb/saveConfigToDb + cache.
- index.js: endpoints superadmin GET/PUT /api/nw/config (secrets nunca em claro;
branco mantém valor), GET /api/nw/clinicas (dropdown), GET /api/nw/status (testar
conexão). server.js passa { superadminGuard }.
Frontend:
- PluginsView: NewwhatsConfigModal dedicado (carrega/salva no backend, dropdown de
clínicas em vez de ID cru, botão Testar conexão). Substitui o form genérico p/ newwhats.
Resolve UX ruim: nada de digitar ID de clínica; config passa a funcionar de fato.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
81 lines
3.9 KiB
JavaScript
81 lines
3.9 KiB
JavaScript
// 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)');
|
|
}
|