116 lines
4.2 KiB
JavaScript
116 lines
4.2 KiB
JavaScript
'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 2–10s 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
|