Enhance giveaway job handling and update environment configuration
- Added support for a new `giveawayCreate` job in the bot's job processing, allowing for giveaways to be created via the WebUI. - Introduced `runGiveawayCreateJob` function to handle the creation of giveaways, ensuring consistent behavior with existing commands. - Updated `.env.example` to clarify the usage of `BOT_TOKEN` for both the bot and WebUI, enhancing documentation for environment variables. - Added `bullmq` dependency in the WebUI package for job management capabilities.
This commit is contained in:
@@ -21,6 +21,7 @@
|
||||
"@radix-ui/react-separator": "^1.1.12",
|
||||
"@radix-ui/react-slot": "^1.3.0",
|
||||
"@radix-ui/react-switch": "^1.3.4",
|
||||
"bullmq": "^5.34.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"dotenv": "^16.4.7",
|
||||
|
||||
@@ -9,6 +9,14 @@ const EnvSchema = z.object({
|
||||
REDIS_URL: z.string().url(),
|
||||
BOT_CLIENT_ID: z.string().min(1),
|
||||
BOT_CLIENT_SECRET: z.string().min(1),
|
||||
/**
|
||||
* Discord bot token. The WebUI does not run a discord.js Client/gateway
|
||||
* connection, but uses this token for narrow, on-demand Discord REST calls
|
||||
* (e.g. building a guild backup snapshot) and to enqueue BullMQ jobs that
|
||||
* the bot process picks up for actions that require a live Client
|
||||
* (posting messages, restoring a backup).
|
||||
*/
|
||||
BOT_TOKEN: z.string().min(1),
|
||||
WEBUI_URL: z.string().url().default('http://localhost:3000'),
|
||||
SESSION_SECRET: z.string().min(32, 'SESSION_SECRET must be at least 32 characters long'),
|
||||
SENTRY_DSN: z.string().optional(),
|
||||
|
||||
76
apps/webui/src/lib/queues.ts
Normal file
76
apps/webui/src/lib/queues.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
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';
|
||||
|
||||
const globalForQueues = globalThis as unknown as {
|
||||
__nexumiGiveawayQueue?: Queue;
|
||||
__nexumiScheduleQueue?: Queue;
|
||||
__nexumiGuildBackupQueue?: Queue;
|
||||
__nexumiGiveawayQueueEvents?: QueueEvents;
|
||||
__nexumiGuildBackupQueueEvents?: 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 });
|
||||
|
||||
/**
|
||||
* 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() });
|
||||
|
||||
globalForQueues.__nexumiGiveawayQueue = giveawayQueue;
|
||||
globalForQueues.__nexumiScheduleQueue = scheduleQueue;
|
||||
globalForQueues.__nexumiGuildBackupQueue = guildBackupQueue;
|
||||
globalForQueues.__nexumiGiveawayQueueEvents = giveawayQueueEvents;
|
||||
globalForQueues.__nexumiGuildBackupQueueEvents = guildBackupQueueEvents;
|
||||
|
||||
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 };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user