Files
CubeAdmin/lib/scheduler/index.ts
2026-03-08 15:49:34 +01:00

76 lines
1.9 KiB
TypeScript

/**
* 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<string, ScheduledTask>();
/** Load all enabled tasks from DB and schedule them. */
export async function initScheduler(): Promise<void> {
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());
}