Refactor scheduler module and enhance scheduled message handling
- Updated `SchedulerValidationError` to include error codes for better error management. - Renamed functions for clarity: `listSchedules` to `listScheduledMessagesDashboard` and `createSchedule` to `createScheduledMessageDashboard`. - Improved validation logic for cron expressions and scheduling times. - Enhanced the `deleteSchedule` function to ensure proper handling of existing scheduled messages. - Adjusted database query order and limits for better performance in fetching scheduled messages.
This commit is contained in:
@@ -3,11 +3,10 @@ import type { ScheduledMessage } from '@prisma/client';
|
||||
import { prisma } from '../prisma';
|
||||
import { scheduleQueue } from '../queues';
|
||||
|
||||
export class SchedulerValidationError extends Error {}
|
||||
|
||||
function isValidCron(pattern: string): boolean {
|
||||
const parts = pattern.trim().split(/\s+/);
|
||||
return parts.length >= 5 && parts.length <= 6;
|
||||
export class SchedulerValidationError extends Error {
|
||||
constructor(public readonly code: string) {
|
||||
super(code);
|
||||
}
|
||||
}
|
||||
|
||||
function toDashboard(schedule: ScheduledMessage): ScheduledMessageDashboard {
|
||||
@@ -24,23 +23,30 @@ function toDashboard(schedule: ScheduledMessage): ScheduledMessageDashboard {
|
||||
};
|
||||
}
|
||||
|
||||
export async function listSchedules(guildId: string): Promise<ScheduledMessageDashboard[]> {
|
||||
export async function listScheduledMessagesDashboard(guildId: string): Promise<ScheduledMessageDashboard[]> {
|
||||
const schedules = await prisma.scheduledMessage.findMany({
|
||||
where: { guildId, enabled: true },
|
||||
orderBy: { createdAt: 'desc' }
|
||||
orderBy: { createdAt: 'asc' },
|
||||
take: 50
|
||||
});
|
||||
return schedules.map(toDashboard);
|
||||
}
|
||||
|
||||
function isValidCron(pattern: string): boolean {
|
||||
const parts = pattern.trim().split(/\s+/);
|
||||
return parts.length >= 5 && parts.length <= 6;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a scheduled message and enqueues the exact `scheduleSend` BullMQ
|
||||
* job the bot's `/schedule create` command would enqueue (same queue, job
|
||||
* name and delay/repeat semantics) - see
|
||||
* `apps/bot/src/modules/scheduler/service.ts#enqueueScheduleJob`. The bot's
|
||||
* existing `scheduleQueueName` worker sends the message, so no bot-side
|
||||
* changes are required.
|
||||
* Mirrors `apps/bot/src/modules/scheduler/service.ts#createScheduledMessage`
|
||||
* job scheduling exactly (same queue, same `scheduleSend` job name, same
|
||||
* `schedule-{id}` job id convention) so schedules created from the
|
||||
* dashboard are picked up by the bot's existing `scheduleWorker` without any
|
||||
* changes on the bot side. Channel permission checks are skipped here since
|
||||
* the WebUI has no discord.js Client - the bot validates the channel again
|
||||
* when the job fires and disables the schedule if it is unusable.
|
||||
*/
|
||||
export async function createSchedule(
|
||||
export async function createScheduledMessageDashboard(
|
||||
guildId: string,
|
||||
creatorId: string,
|
||||
input: ScheduledMessageDashboardCreate
|
||||
@@ -49,12 +55,12 @@ export async function createSchedule(
|
||||
|
||||
const cron = input.cron?.trim() || null;
|
||||
if (cron && !isValidCron(cron)) {
|
||||
throw new SchedulerValidationError('Invalid cron expression (expects 5-6 space separated fields)');
|
||||
throw new SchedulerValidationError('invalid_cron');
|
||||
}
|
||||
|
||||
const runAt = !cron && input.runAt ? new Date(input.runAt) : null;
|
||||
if (!cron && runAt && runAt.getTime() <= Date.now()) {
|
||||
throw new SchedulerValidationError('runAt must be in the future');
|
||||
if (!cron && !runAt) {
|
||||
throw new SchedulerValidationError('missing_schedule_time');
|
||||
}
|
||||
|
||||
const schedule = await prisma.scheduledMessage.create({
|
||||
@@ -63,7 +69,7 @@ export async function createSchedule(
|
||||
channelId: input.channelId,
|
||||
creatorId,
|
||||
content: input.content?.trim() || null,
|
||||
rolePingId: input.rolePingId ? input.rolePingId : null,
|
||||
rolePingId: input.rolePingId || null,
|
||||
cron,
|
||||
runAt
|
||||
}
|
||||
@@ -95,14 +101,14 @@ export async function createSchedule(
|
||||
return toDashboard(updated);
|
||||
}
|
||||
|
||||
export async function deleteSchedule(guildId: string, scheduleId: string): Promise<boolean> {
|
||||
const schedule = await prisma.scheduledMessage.findFirst({ where: { id: scheduleId, guildId } });
|
||||
if (!schedule) {
|
||||
export async function deleteScheduledMessageDashboard(guildId: string, scheduleId: string): Promise<boolean> {
|
||||
const existing = await prisma.scheduledMessage.findFirst({ where: { id: scheduleId, guildId } });
|
||||
if (!existing) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (schedule.jobId) {
|
||||
const job = await scheduleQueue.getJob(schedule.jobId);
|
||||
if (existing.jobId) {
|
||||
const job = await scheduleQueue.getJob(existing.jobId);
|
||||
await job?.remove();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user