import { Queue, QueueEvents } from 'bullmq'; import { redis } from './redis'; /** * BullMQ queue/job names mirrored 1:1 from `apps/bot/src/queues.ts` and * `apps/bot/src/jobs.ts`. The WebUI has no discord.js Client, so any * dashboard action that needs to touch Discord (posting a giveaway message, * drawing winners, restoring a guild backup) is enqueued here and picked up * by the bot's existing workers - the WebUI never talks to Discord directly * for these actions. * * Queues and QueueEvents are created lazily on first use. Eager construction * at import time would open Redis connections during `next build` (where * Redis is intentionally unavailable), flooding the build log with * ECONNREFUSED errors. */ export const giveawayQueueName = 'giveaways'; export const scheduleQueueName = 'schedules'; export const guildBackupQueueName = 'guild-backups'; export const suggestionsQueueName = 'suggestions'; export const messagesQueueName = 'messages'; export const verificationQueueName = 'verification'; export const automodQueueName = 'automod'; const globalForQueues = globalThis as unknown as { __nexumiGiveawayQueue?: Queue; __nexumiScheduleQueue?: Queue; __nexumiGuildBackupQueue?: Queue; __nexumiSuggestionsQueue?: Queue; __nexumiMessagesQueue?: Queue; __nexumiVerificationQueue?: Queue; __nexumiAutomodQueue?: Queue; __nexumiGiveawayQueueEvents?: QueueEvents; __nexumiGuildBackupQueueEvents?: QueueEvents; __nexumiSuggestionsQueueEvents?: QueueEvents; __nexumiMessagesQueueEvents?: QueueEvents; }; function queueConnection() { return redis; } /** QueueEvents needs its own connection; duplicate inherits lazyConnect. */ function queueEventsConnection() { return redis.duplicate(); } export function getGiveawayQueue(): Queue { globalForQueues.__nexumiGiveawayQueue ??= new Queue(giveawayQueueName, { connection: queueConnection() }); return globalForQueues.__nexumiGiveawayQueue; } export function getScheduleQueue(): Queue { globalForQueues.__nexumiScheduleQueue ??= new Queue(scheduleQueueName, { connection: queueConnection() }); return globalForQueues.__nexumiScheduleQueue; } export function getGuildBackupQueue(): Queue { globalForQueues.__nexumiGuildBackupQueue ??= new Queue(guildBackupQueueName, { connection: queueConnection() }); return globalForQueues.__nexumiGuildBackupQueue; } export function getSuggestionsQueue(): Queue { globalForQueues.__nexumiSuggestionsQueue ??= new Queue(suggestionsQueueName, { connection: queueConnection() }); return globalForQueues.__nexumiSuggestionsQueue; } export function getMessagesQueue(): Queue { globalForQueues.__nexumiMessagesQueue ??= new Queue(messagesQueueName, { connection: queueConnection() }); return globalForQueues.__nexumiMessagesQueue; } export function getVerificationQueue(): Queue { globalForQueues.__nexumiVerificationQueue ??= new Queue(verificationQueueName, { connection: queueConnection() }); return globalForQueues.__nexumiVerificationQueue; } export function getAutomodQueue(): Queue { globalForQueues.__nexumiAutomodQueue ??= new Queue(automodQueueName, { connection: queueConnection() }); return globalForQueues.__nexumiAutomodQueue; } /** * QueueEvents instances are only needed for jobs where the dashboard needs * to wait for the bot to finish (e.g. ending a giveaway so the winners list * is immediately visible). */ export function getGiveawayQueueEvents(): QueueEvents { globalForQueues.__nexumiGiveawayQueueEvents ??= new QueueEvents(giveawayQueueName, { connection: queueEventsConnection() }); return globalForQueues.__nexumiGiveawayQueueEvents; } export function getGuildBackupQueueEvents(): QueueEvents { globalForQueues.__nexumiGuildBackupQueueEvents ??= new QueueEvents(guildBackupQueueName, { connection: queueEventsConnection() }); return globalForQueues.__nexumiGuildBackupQueueEvents; } export function getSuggestionsQueueEvents(): QueueEvents { globalForQueues.__nexumiSuggestionsQueueEvents ??= new QueueEvents(suggestionsQueueName, { connection: queueEventsConnection() }); return globalForQueues.__nexumiSuggestionsQueueEvents; } export function getMessagesQueueEvents(): QueueEvents { globalForQueues.__nexumiMessagesQueueEvents ??= new QueueEvents(messagesQueueName, { connection: queueEventsConnection() }); return globalForQueues.__nexumiMessagesQueueEvents; } const DEFAULT_WAIT_TIMEOUT_MS = 15_000; /** * Adds a job and waits (bounded) for the bot worker to finish it, so the * dashboard can immediately reflect the result instead of polling. Falls * back to "not confirmed" (does not throw) on timeout - the DB row created * by the worker is still the source of truth and will show up on next load. */ export async function addJobAndAwait( queue: Queue, queueEvents: QueueEvents, jobName: string, data: unknown, options: Parameters[2] = {}, timeoutMs = DEFAULT_WAIT_TIMEOUT_MS ): Promise<{ confirmed: boolean; result: T | null }> { const job = await queue.add(jobName, data, options); try { const result = (await job.waitUntilFinished(queueEvents, timeoutMs)) as T; return { confirmed: true, result }; } catch { return { confirmed: false, result: null }; } }