import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; import { mydb } from "../../lib/mySupabase.ts"; import { corsHeaders, jsonResponse, errorResponse } from "../../lib/utils.ts"; /** * POST /notifications-worker * Worker que processa fila de notificações pendentes * Deve ser executado via cron job a cada 1-5 minutos * * Processa: * - SMS via Twilio * - Email via SendGrid/Resend * - WhatsApp via Twilio * - Push notifications */ const TWILIO_SID = Deno.env.get("TWILIO_SID"); const TWILIO_AUTH_TOKEN = Deno.env.get("TWILIO_AUTH_TOKEN"); const TWILIO_FROM = Deno.env.get("TWILIO_FROM"); async function sendSMS(to: string, message: string) { if (!TWILIO_SID || !TWILIO_AUTH_TOKEN || !TWILIO_FROM) { console.warn("[SMS] Twilio não configurado"); return { success: false, error: "Twilio not configured" }; } const url = `https://api.twilio.com/2010-04-01/Accounts/${TWILIO_SID}/Messages.json`; const auth = btoa(`${TWILIO_SID}:${TWILIO_AUTH_TOKEN}`); try { const res = await fetch(url, { method: "POST", headers: { Authorization: `Basic ${auth}`, "Content-Type": "application/x-www-form-urlencoded", }, body: new URLSearchParams({ To: to, From: TWILIO_FROM!, Body: message, }), }); const data = await res.json(); if (!res.ok) { return { success: false, error: data.message || "SMS failed" }; } return { success: true, sid: data.sid }; } catch (error) { console.error("[SMS] Error:", error); return { success: false, error: String(error) }; } } async function sendEmail(to: string, subject: string, body: string) { // TODO: Implementar SendGrid ou Resend console.log("[Email] Would send to:", to, subject); return { success: true, message: "Email sending not implemented yet" }; } async function sendWhatsApp(to: string, message: string) { if (!TWILIO_SID || !TWILIO_AUTH_TOKEN) { return { success: false, error: "Twilio not configured" }; } const url = `https://api.twilio.com/2010-04-01/Accounts/${TWILIO_SID}/Messages.json`; const auth = btoa(`${TWILIO_SID}:${TWILIO_AUTH_TOKEN}`); try { const res = await fetch(url, { method: "POST", headers: { Authorization: `Basic ${auth}`, "Content-Type": "application/x-www-form-urlencoded", }, body: new URLSearchParams({ To: `whatsapp:${to}`, From: `whatsapp:${TWILIO_FROM}`, Body: message, }), }); const data = await res.json(); if (!res.ok) { return { success: false, error: data.message || "WhatsApp failed" }; } return { success: true, sid: data.sid }; } catch (error) { return { success: false, error: String(error) }; } } serve(async (req) => { if (req.method === "OPTIONS") { return new Response("ok", { status: 200, headers: corsHeaders() }); } try { console.log("[Worker] Iniciando processamento de notificações..."); // 1. Buscar notificações pendentes ou agendadas para agora const now = new Date().toISOString(); const { data: notifications, error: fetchError } = await mydb .from("notifications_queue") .select("*") .eq("status", "pending") .or(`scheduled_at.is.null,scheduled_at.lte.${now}`) .lt("attempts", 3) .order("created_at", { ascending: true }) .limit(50); // Processa 50 por vez if (fetchError) { console.error("[Worker] Erro ao buscar notificações:", fetchError); return errorResponse(fetchError.message, 500); } if (!notifications || notifications.length === 0) { console.log("[Worker] Nenhuma notificação pendente"); return jsonResponse({ success: true, processed: 0, message: "No pending notifications", }); } console.log(`[Worker] Processando ${notifications.length} notificações...`); let processed = 0; let failed = 0; const results = []; for (const notification of notifications) { const { id, type, payload, attempts } = notification; // Marcar como processando await mydb .from("notifications_queue") .update({ status: "processing" }) .eq("id", id); let result; let success = false; try { switch (type) { case "sms": result = await sendSMS(payload.to, payload.message); success = result.success; break; case "email": result = await sendEmail(payload.to, payload.subject, payload.body); success = result.success; break; case "whatsapp": result = await sendWhatsApp(payload.to, payload.message); success = result.success; break; case "push": // TODO: Implementar push notifications (Firebase, OneSignal, etc) result = { success: false, error: "Push not implemented" }; break; default: result = { success: false, error: "Unknown notification type" }; } if (success) { // Marcar como enviada await mydb .from("notifications_queue") .update({ status: "sent", sent_at: new Date().toISOString(), attempts: attempts + 1, }) .eq("id", id); processed++; } else { // Incrementar tentativas e marcar erro const newAttempts = attempts + 1; const finalStatus = newAttempts >= 3 ? "failed" : "pending"; await mydb .from("notifications_queue") .update({ status: finalStatus, attempts: newAttempts, last_error: result.error || "Unknown error", }) .eq("id", id); failed++; } results.push({ id, type, success, error: result.error || null, }); } catch (error) { console.error(`[Worker] Erro ao processar notificação ${id}:`, error); await mydb .from("notifications_queue") .update({ status: "pending", attempts: attempts + 1, last_error: String(error), }) .eq("id", id); failed++; } } console.log( `[Worker] Concluído: ${processed} enviadas, ${failed} falharam` ); return jsonResponse({ success: true, processed, failed, total: notifications.length, results, }); } catch (error: unknown) { console.error("[Worker] Erro geral:", error); const err = error as Error; return errorResponse(err.message, 500); } });