// 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, createAccountsRoute, provisionNewwhatsAccount } from './proxy.js' import { getConfig, isConfigured, loadConfigFromDb, saveConfigToDb } from './config.js' import { Readable } from 'node:stream' export { syncKnowledge, buildKnowledgeText } from './sync-knowledge.js' export { createWsTunnel } from './proxy.js' // Provisiona (eager) a conta do email no motor NewWhats, best-effort e idempotente. // Chamado no cadastro de usuário/clínica do scoreodonto: "assim que a conta nasce // aqui, já nasce lá — pronta esperando quando a pessoa for mexer com WhatsApp". export async function provisionNewwhatsAccountEager(pool, email) { try { const cfg = getConfig() if (!cfg.motorUrl || !cfg.integrationKey || !email) return false return await provisionNewwhatsAccount(pool, cfg, email) } catch { return false } } import { createAgendaBridge } from './agenda-bridge.js' import { createAgendaConfig } from './agenda-config.js' import { ensureAgendaSchema } from './agenda-schema.js' // Valida uma chave contra o motor. Envia um email-sonda (a chave mestra exige // x-nw-email). 200 = ok com dados; 404 = chave válida mas email-sonda inexistente // (= chave OK); 401 = chave inválida. async function testMotorKey(motorUrl, key) { try { const r = await fetch(`${motorUrl}/api/ext/v1/sessions`, { headers: { 'x-nw-key': key, 'x-nw-email': 'probe@nw.local' }, }); if (r.ok || r.status === 404) return { ok: true, detail: 'Conexão OK' }; return { ok: false, detail: `Motor respondeu ${r.status}` }; } catch (e) { return { ok: false, detail: `Motor inacessível: ${e.message}` }; } } export function registerNewwhats(app, pool, opts = {}) { // passthrough middleware caso o guard não seja fornecido (dev) const superadminGuard = opts.superadminGuard || ((_req, _res, next) => next()); // ── Ponte de agenda (motor → agenda real do scoreodonto) ─────────────────── // Auth própria por segredo compartilhado (x-nw-agenda-secret). Server-to-server. app.use('/api/nw/agenda', createAgendaBridge(pool)); // ── Schema de horários/feriados (idempotente) + config (dono/dentista) ───── // clinicas_horarios + feriados; usados pela agenda-bridge (Secretária) e pelas // telas de configuração do scoreodonto. ensureAgendaSchema(pool).catch((e) => console.error('[newwhats] schema agenda:', e?.message)); app.use('/api/nw/agenda-config', createAgendaConfig(pool)); // ── Proxy de MÍDIA: /api/nw/media/* → motor ──────────────────────────────── // A mídia/avatar vivem no motor (Wasabi ou local). O Wasabi é servido por // /api/storage/view/* (plugin UnifiedStorageProvider, que cobre Wasabi + fallback // local); a mídia local ANTIGA fica no /media estático. Tentamos o storage do // plugin primeiro e caímos no /media se der 404. Same-origin no /