Files

141 lines
5.3 KiB
JavaScript
Raw Permalink 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 cron = require('node-cron')
const { query, execute } = require('../database/postgres')
const RaffleResult = require('../models/RaffleResult')
const Ticket = require('../models/Ticket')
const WhatsappCore = require('../services/whatsappService')
const BASE_URL = process.env.APP_URL || 'http://localhost:4001'
function startWorker() {
console.log('⏳ Worker de Sorteio Automático iniciado (node-cron)')
// ── A cada minuto: sorteio + liberar reservas + alerta expiração ───────────
cron.schedule('* * * * *', async () => {
try {
await Ticket.releaseExpiredReservations()
await _alertReservationExpiring()
const raffles = await query("SELECT id, total_numeros, end_at FROM raffles WHERE status = 'active'")
for (const raffle of raffles) {
let shouldDraw = false
let reason = ''
if (raffle.end_at && new Date() >= new Date(raffle.end_at)) {
shouldDraw = true
reason = 'Data limite atingida'
}
if (!shouldDraw) {
const totalSold = await Ticket.countByRaffle(raffle.id)
if (totalSold >= raffle.total_numeros) {
shouldDraw = true
reason = 'Lotação máxima atingida (100% vendido)'
}
}
if (shouldDraw) {
console.log(`🤖 [Worker] Iniciando sorteio automático #${raffle.id} - Motivo: ${reason}`)
try {
const result = await RaffleResult.draw(raffle.id, '127.0.0.1')
console.log(`🎉 [Worker] Sorteio #${raffle.id} finalizado! Ganhador: Ticket #${result.winner_ticket_id} (Número ${result.winner_number})`)
} catch (drawError) {
if (drawError.message === 'Nenhum ticket vendido para este sorteio') {
console.log(`⚠️ [Worker] Sorteio #${raffle.id} ignorado: nenhum ticket vendido.`)
} else {
console.error(`❌ [Worker] Erro ao executar sorteio #${raffle.id}:`, drawError.message)
}
}
}
}
} catch (e) {
console.error('❌ [Worker] Falha na checagem cron (minuto):', e.message)
}
})
// ── A cada 10 minutos: notificação "Últimas Horas" ─────────────────────────
cron.schedule('*/10 * * * *', async () => {
try {
await _alertLastHours()
} catch (e) {
console.error('❌ [Worker] Falha na checagem last_hours:', e.message)
}
})
}
// ─── Alerta "Últimas Horas" ───────────────────────────────────────────────────
// Dispara 1× por sorteio cujo end_at está entre agora e +2h,
// e que ainda não foi notificado (last_hours_notif_sent IS NULL).
async function _alertLastHours() {
const rows = await query(
`SELECT id, titulo FROM raffles
WHERE status = 'active'
AND end_at IS NOT NULL
AND end_at BETWEEN NOW() AND NOW() + INTERVAL '2 hours'
AND last_hours_notif_sent IS NULL`
)
for (const raffle of (rows ?? [])) {
// Marca antes de enviar — evita duplos em caso de erro parcial
await execute(`UPDATE raffles SET last_hours_notif_sent = NOW() WHERE id = $1`, [raffle.id])
const users = await query(
`SELECT DISTINCT u.id, u.telefone, u.cpf, u.nome
FROM users u
WHERE u.telefone IS NOT NULL
AND u.whatsapp_optin = TRUE
AND u.cpf NOT IN (
SELECT DISTINCT t.usuario_cpf FROM tickets t
WHERE t.raffle_id = $1 AND t.status = 'sold'
)`,
[raffle.id]
)
const link = `${BASE_URL}/sorteio`
for (const u of (users ?? [])) {
WhatsappCore.sendNotification({
userId: u.id, phone: u.telefone, type: 'last_hours', userCpf: u.cpf,
variables: { raffleName: raffle.titulo, link },
}).catch(() => {})
}
console.log(`⏰ [Worker] last_hours rifa #${raffle.id}${(users ?? []).length} notificação(ões) enfileirada(s)`)
}
}
// ─── Alerta "Reserva Expirando" ───────────────────────────────────────────────
// Reservas com reserved_until entre 50s e 70s no futuro (≈ 1 min restante).
async function _alertReservationExpiring() {
const rows = await query(
`SELECT DISTINCT t.usuario_telefone, t.usuario_cpf, t.usuario_nome,
o.pix_copy_paste, o.qr_code_url, r.titulo as raffle_titulo
FROM tickets t
JOIN orders o ON o.id = t.order_id
JOIN raffles r ON r.id = t.raffle_id
WHERE t.status = 'reserved'
AND o.status = 'pending'
AND t.usuario_telefone IS NOT NULL
AND t.reserved_until BETWEEN NOW() + INTERVAL '50 seconds'
AND NOW() + INTERVAL '70 seconds'`
)
for (const t of (rows ?? [])) {
WhatsappCore.sendNotification({
userId: null,
phone: t.usuario_telefone,
type: 'reservation_expiring',
userCpf: t.usuario_cpf,
variables: {
raffleName: t.raffle_titulo,
link: t.qr_code_url || `${BASE_URL}/area-do-usuario`,
},
}).catch(() => {})
}
if ((rows ?? []).length > 0) {
console.log(`⚠️ [Worker] reservation_expiring: ${rows.length} alerta(s) enviado(s)`)
}
}
module.exports = { startWorker }