// 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
/