/** * Task scheduler using node-cron. * Loads enabled tasks from the DB on startup and registers them. */ import cron, { ScheduledTask } from "node-cron"; import { db } from "@/lib/db"; import { scheduledTasks } from "@/lib/db/schema"; import { eq } from "drizzle-orm"; import { rconClient } from "@/lib/minecraft/rcon"; import { sanitizeRconCommand } from "@/lib/security/sanitize"; const activeJobs = new Map(); /** Load all enabled tasks from DB and schedule them. */ export async function initScheduler(): Promise { const tasks = await db .select() .from(scheduledTasks) .where(eq(scheduledTasks.isEnabled, true)); for (const task of tasks) { scheduleTask(task.id, task.cronExpression, task.command); } } /** Schedule a single task. Returns false if cron expression is invalid. */ export function scheduleTask( id: string, expression: string, command: string, ): boolean { if (!cron.validate(expression)) return false; // Stop existing job if any stopTask(id); const job = cron.schedule(expression, async () => { try { const safeCmd = sanitizeRconCommand(command); await rconClient.sendCommand(safeCmd); await db .update(scheduledTasks) .set({ lastRun: Date.now() }) .where(eq(scheduledTasks.id, id)); } catch (err) { console.error(`[Scheduler] Task ${id} failed:`, err); } }); activeJobs.set(id, job); return true; } /** Stop and remove a scheduled task. */ export function stopTask(id: string): void { const existing = activeJobs.get(id); if (existing) { existing.stop(); activeJobs.delete(id); } } /** Stop all active jobs. */ export function stopAllTasks(): void { for (const [id, job] of activeJobs.entries()) { job.stop(); activeJobs.delete(id); } } /** List active job IDs. */ export function getActiveTaskIds(): string[] { return Array.from(activeJobs.keys()); }