- Introduced a new `SuggestionStatusUpdateJob` type and corresponding worker to handle suggestion status updates in the bot's job processing. - Added `suggestionsQueue` and `suggestionsQueueEvents` for managing suggestion-related jobs in both the bot and WebUI. - Implemented `runSuggestionStatusUpdateJob` to facilitate staff actions on suggestions via the WebUI, ensuring consistent behavior with existing commands. - Enhanced error handling for suggestion actions to improve user experience and reliability.
89 lines
3.6 KiB
TypeScript
89 lines
3.6 KiB
TypeScript
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.
|
|
*/
|
|
export const giveawayQueueName = 'giveaways';
|
|
export const scheduleQueueName = 'schedules';
|
|
export const guildBackupQueueName = 'guild-backups';
|
|
export const suggestionsQueueName = 'suggestions';
|
|
|
|
const globalForQueues = globalThis as unknown as {
|
|
__nexumiGiveawayQueue?: Queue;
|
|
__nexumiScheduleQueue?: Queue;
|
|
__nexumiGuildBackupQueue?: Queue;
|
|
__nexumiSuggestionsQueue?: Queue;
|
|
__nexumiGiveawayQueueEvents?: QueueEvents;
|
|
__nexumiGuildBackupQueueEvents?: QueueEvents;
|
|
__nexumiSuggestionsQueueEvents?: QueueEvents;
|
|
};
|
|
|
|
export const giveawayQueue: Queue =
|
|
globalForQueues.__nexumiGiveawayQueue ?? new Queue(giveawayQueueName, { connection: redis });
|
|
|
|
export const scheduleQueue: Queue =
|
|
globalForQueues.__nexumiScheduleQueue ?? new Queue(scheduleQueueName, { connection: redis });
|
|
|
|
export const guildBackupQueue: Queue =
|
|
globalForQueues.__nexumiGuildBackupQueue ?? new Queue(guildBackupQueueName, { connection: redis });
|
|
|
|
export const suggestionsQueue: Queue =
|
|
globalForQueues.__nexumiSuggestionsQueue ?? new Queue(suggestionsQueueName, { connection: redis });
|
|
|
|
/**
|
|
* 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). They open their own Redis connection, so they are
|
|
* cached on the global object the same way as the queues themselves.
|
|
*/
|
|
export const giveawayQueueEvents: QueueEvents =
|
|
globalForQueues.__nexumiGiveawayQueueEvents ??
|
|
new QueueEvents(giveawayQueueName, { connection: redis.duplicate() });
|
|
|
|
export const guildBackupQueueEvents: QueueEvents =
|
|
globalForQueues.__nexumiGuildBackupQueueEvents ??
|
|
new QueueEvents(guildBackupQueueName, { connection: redis.duplicate() });
|
|
|
|
export const suggestionsQueueEvents: QueueEvents =
|
|
globalForQueues.__nexumiSuggestionsQueueEvents ??
|
|
new QueueEvents(suggestionsQueueName, { connection: redis.duplicate() });
|
|
|
|
globalForQueues.__nexumiGiveawayQueue = giveawayQueue;
|
|
globalForQueues.__nexumiScheduleQueue = scheduleQueue;
|
|
globalForQueues.__nexumiGuildBackupQueue = guildBackupQueue;
|
|
globalForQueues.__nexumiSuggestionsQueue = suggestionsQueue;
|
|
globalForQueues.__nexumiGiveawayQueueEvents = giveawayQueueEvents;
|
|
globalForQueues.__nexumiGuildBackupQueueEvents = guildBackupQueueEvents;
|
|
globalForQueues.__nexumiSuggestionsQueueEvents = suggestionsQueueEvents;
|
|
|
|
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<T = unknown>(
|
|
queue: Queue,
|
|
queueEvents: QueueEvents,
|
|
jobName: string,
|
|
data: unknown,
|
|
options: Parameters<Queue['add']>[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 };
|
|
}
|
|
}
|