import { prisma } from '@/lib/prisma' import { sendSms } from '@/lib/semaphore' import { formatPhone } from '@/lib/utils' const MAX_ATTEMPTS = 3 export async function processQueue(): Promise<{ processed: number; failed: number }> { let processed = 0 let failed = 0 const jobs = await prisma.smsQueue.findMany({ where: { status: { in: ['PENDING', 'RETRYING'] }, attempts: { lt: MAX_ATTEMPTS }, scheduledAt: { lte: new Date() }, }, include: { client: true }, orderBy: { scheduledAt: 'asc' }, take: 50, }) for (const job of jobs) { await prisma.smsQueue.update({ where: { id: job.id }, data: { status: 'PROCESSING' }, }) const client = job.client if (!client.semaphoreKey || !client.isActive) { await prisma.smsQueue.update({ where: { id: job.id }, data: { status: 'FAILED', lastError: 'Client has no Semaphore API key configured', processedAt: new Date(), }, }) await createLog(job.id, client.id, job.parentPhone, job.message || '', 'FAILED', null, 'No Semaphore key') failed++ continue } const message = job.message || `${job.event === 'time_in' ? '✅ Time In' : '🔴 Time Out'}: ${job.studentName} (ID: ${job.studentId}) at ${new Date(job.timestamp).toLocaleString('en-PH', { timeZone: 'Asia/Manila' })}` const phone = formatPhone(job.parentPhone) const result = await sendSms(client.semaphoreKey, client.senderName || 'NFC-HUB', phone, message) const newAttempts = job.attempts + 1 if (result.success) { await prisma.smsQueue.update({ where: { id: job.id }, data: { status: 'SENT', attempts: newAttempts, processedAt: new Date(), lastError: null }, }) await createLog(job.id, client.id, phone, message, 'SUCCESS', result.messageId, null) processed++ } else { const nextStatus = newAttempts >= MAX_ATTEMPTS ? 'FAILED' : 'RETRYING' const retryDelay = Math.pow(2, newAttempts) * 60 * 1000 const scheduledAt = nextStatus === 'RETRYING' ? new Date(Date.now() + retryDelay) : new Date() await prisma.smsQueue.update({ where: { id: job.id }, data: { status: nextStatus, attempts: newAttempts, lastError: result.error, scheduledAt, processedAt: nextStatus === 'FAILED' ? new Date() : undefined, }, }) if (nextStatus === 'FAILED') { await createLog(job.id, client.id, phone, message, 'FAILED', null, result.error || 'Max retries reached') failed++ } } } return { processed, failed } } async function createLog( queueId: string, clientId: string, phone: string, message: string, status: 'SUCCESS' | 'FAILED', semaphoreId: string | null | undefined, errorReason: string | null | undefined ) { const existing = await prisma.smsLog.findUnique({ where: { queueId } }) if (existing) return await prisma.smsLog.create({ data: { queueId, clientId, phone, message, status, semaphoreId: semaphoreId ?? null, errorReason: errorReason ?? null, }, }) }