Files
mercado.clube67.com/backend/workers/whatsappWorker.js
T

116 lines
4.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict'
const { Worker } = require('bullmq')
const Redis = require('ioredis')
const { queryOne, execute } = require('../database/postgres')
const WhatsappLog = require('../models/WhatsappLog')
const connection = new Redis({
host: process.env.REDIS_HOST || '127.0.0.1',
port: process.env.REDIS_PORT || 6379,
maxRetriesPerRequest: null,
})
const delay = (ms) => new Promise(res => setTimeout(res, ms))
/**
* Lê URL e token do motor NewWhats.
* Prioridade: plugin_configs (banco) → variáveis de ambiente → defaults de dev.
* Isso garante consistência com o proxy REST/WS que também lê do banco.
*/
async function getMotorConfig() {
try {
const [urlRow, tokenRow] = await Promise.all([
queryOne("SELECT value FROM plugin_configs WHERE plugin_id = 'newwhats' AND key = 'newwhats_url'"),
queryOne("SELECT value FROM plugin_configs WHERE plugin_id = 'newwhats' AND key = 'api_token'"),
])
return {
apiUrl: urlRow?.value?.replace(/\/$/, '')
|| process.env.NEWWHATS_API_URL
|| 'http://localhost:8008',
token: tokenRow?.value
|| process.env.NEWWHATS_API_TOKEN
|| 'local',
}
} catch {
// Banco indisponível — usa env var como fallback seguro
return {
apiUrl: process.env.NEWWHATS_API_URL || 'http://localhost:8008',
token: process.env.NEWWHATS_API_TOKEN || 'local',
}
}
}
// ── Worker BullMQ ─────────────────────────────────────────────────────────────
const whatsappWorker = new Worker(
'whatsapp-notifications',
async (job) => {
const { logId, payload } = job.data
console.log(`[WhatsApp Worker] Job ${job.id} iniciado — número ${payload.number} (logId: ${logId})`)
await WhatsappLog.updateStatus(logId, 'processing')
// Rate limit: delay aleatório de 210s para evitar ban do WhatsApp
const randomDelay = Math.floor(Math.random() * 8000) + 2000
await delay(randomDelay)
const { apiUrl, token } = await getMotorConfig()
try {
const res = await fetch(`${apiUrl}/api/messages/send`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
body: JSON.stringify(payload),
})
const resultBody = await res.text()
if (!res.ok) {
throw new Error(`HTTP ${res.status}: ${resultBody}`)
}
await WhatsappLog.updateStatus(logId, 'sent', resultBody)
console.log(`[WhatsApp Worker] Job ${job.id} CONCLUÍDO — enviado para ${payload.number}`)
return resultBody
} catch (error) {
console.error(`[WhatsApp Worker] Job ${job.id} FALHOU (tentativa ${job.attemptsMade + 1}):`, error.message)
const isFinal = job.attemptsMade + 1 >= (job.opts?.attempts ?? 3)
await WhatsappLog.updateStatus(logId, isFinal ? 'failed' : 'retrying', error.message)
if (isFinal) {
console.error(`[WhatsApp Worker] ⚠️ Falha DEFINITIVA no job ${job.id} para ${payload.number} — logId ${logId}`)
// Persiste falha definitiva para rastreamento e alerta no admin
execute(
`INSERT INTO notification_errors (job_id, log_id, phone, error_message, payload)
VALUES ($1, $2, $3, $4, $5)`,
[String(job.id), logId ?? null, payload.number ?? null, error.message, JSON.stringify(payload)]
).catch(e => console.error('[WhatsApp Worker] Erro ao gravar notification_errors:', e.message))
}
throw error // Aciona backoff exponencial do BullMQ
}
},
{
connection,
concurrency: parseInt(process.env.WHATSAPP_WORKER_CONCURRENCY || '1', 10),
}
)
// ── Eventos de ciclo de vida ──────────────────────────────────────────────────
whatsappWorker.on('failed', (job, err) => {
console.error(`[WhatsApp Worker] Evento failed — job ${job?.id}: ${err.message}`)
})
whatsappWorker.on('completed', (job) => {
console.log(`[WhatsApp Worker] Evento completed — job ${job.id} removido da fila`)
})
module.exports = whatsappWorker