/** * Background Job: Scheduled Campaign Processor * Processes scheduled campaigns when their time arrives */ import {CampaignStatus} from '@plunk/db'; import type {ScheduledCampaignJobData} from '@plunk/types'; import {type Job, Worker} from 'bullmq'; import signale from 'signale'; import {prisma} from '../database/prisma.js'; import {CampaignService} from '../services/CampaignService.js'; import {scheduledQueue} from '../services/QueueService.js'; export function createScheduledCampaignWorker() { const worker = new Worker( scheduledQueue.name, async (job: Job) => { const {campaignId} = job.data; signale.info(`[SCHEDULED-PROCESSOR] Processing scheduled campaign ${campaignId}`); // Get campaign with project const campaign = await prisma.campaign.findUnique({ where: {id: campaignId}, include: { project: { select: {disabled: true, id: true, name: true}, }, }, }); if (!campaign) { signale.warn(`[SCHEDULED-PROCESSOR] Campaign ${campaignId} not found, skipping`); return; } // Check if project is disabled if (campaign.project.disabled) { signale.warn( `[SCHEDULED-PROCESSOR] Project ${campaign.projectId} (${campaign.project.name}) is disabled, cancelling campaign ${campaignId}`, ); await prisma.campaign.update({ where: {id: campaignId}, data: {status: CampaignStatus.CANCELLED}, }); return; } // Verify campaign is still in SCHEDULED status if (campaign.status !== CampaignStatus.SCHEDULED) { signale.warn( `[SCHEDULED-PROCESSOR] Campaign ${campaignId} is not in SCHEDULED status (${campaign.status}), skipping`, ); return; } // Start sending the campaign await CampaignService.startSending(campaign.projectId, campaignId); signale.info(`[SCHEDULED-PROCESSOR] Started sending campaign ${campaignId}`); }, { connection: scheduledQueue.opts.connection, concurrency: 2, // Process up to 2 scheduled campaigns concurrently }, ); worker.on('completed', job => { signale.info(`[SCHEDULED-PROCESSOR] Job ${job.id} completed`); }); worker.on('failed', (job, err) => { signale.error(`[SCHEDULED-PROCESSOR] Job ${job?.id} failed:`, err.message); }); worker.on('error', err => { signale.error('[SCHEDULED-PROCESSOR] Worker error:', err); }); return worker; }