135 lines
3.5 KiB
TypeScript
135 lines
3.5 KiB
TypeScript
import { Cron } from "croner";
|
|
import { eq } from "drizzle-orm";
|
|
import { db } from "../db/db.controller.js";
|
|
import { jobAuditLog } from "../db/schema/auditLog.schema.js";
|
|
import { createLogger } from "../logger/logger.controller.js";
|
|
|
|
// example createJob
|
|
// createCronJob("test Cron", "*/5 * * * * *", async () => {
|
|
// console.log("help");
|
|
// });
|
|
|
|
export interface JobInfo {
|
|
name: string;
|
|
schedule: string;
|
|
nextRun: Date | null;
|
|
isRunning: boolean;
|
|
}
|
|
|
|
// Store running cronjobs
|
|
export const runningCrons: Record<string, Cron> = {};
|
|
|
|
// how to se the times
|
|
// * ┌──────────────── (optional) second (0 - 59) \n
|
|
// * │ ┌────────────── minute (0 - 59)
|
|
// * │ │ ┌──────────── hour (0 - 23)
|
|
// * │ │ │ ┌────────── day of month (1 - 31)
|
|
// * │ │ │ │ ┌──────── month (1 - 12, JAN-DEC)
|
|
// * │ │ │ │ │ ┌────── day of week (0 - 6, SUN-Mon)
|
|
// * │ │ │ │ │ │ (0 to 6 are Sunday to Saturday; 7 is Sunday, the same as 0)
|
|
// * │ │ │ │ │ │ ┌──── (optional) year (1 - 9999)
|
|
// * │ │ │ │ │ │ │
|
|
// * * 05 * * * * *
|
|
/**
|
|
*
|
|
* @param name Name of the job we want to run
|
|
* @param schedule Cron expression (example: `*\/5 * * * * *`)
|
|
* @param task Async function that will run
|
|
*/
|
|
export const createCronJob = async (
|
|
name: string,
|
|
schedule: string, // cron string with 8 8 IE: */5 * * * * * every 5th second
|
|
task: () => Promise<void>, // what function are we passing over
|
|
) => {
|
|
// get the timezone based on the os timezone set
|
|
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
const log = createLogger({ module: "system", subModule: "croner" });
|
|
|
|
// Destroy existing job if it exist
|
|
if (runningCrons[name]) {
|
|
runningCrons[name].stop();
|
|
}
|
|
|
|
// Create new job with Croner
|
|
runningCrons[name] = new Cron(
|
|
schedule,
|
|
{
|
|
timezone: timeZone,
|
|
catch: true, // Prevents unhandled rejections
|
|
name: name,
|
|
},
|
|
async () => {
|
|
const startedAt = new Date();
|
|
const start = Date.now();
|
|
|
|
let executionId: string = "";
|
|
|
|
try {
|
|
const [execution] = await db
|
|
.insert(jobAuditLog)
|
|
.values({
|
|
jobName: name,
|
|
startedAt,
|
|
status: "running",
|
|
})
|
|
.returning();
|
|
|
|
executionId = execution?.id as string;
|
|
|
|
await task?.();
|
|
|
|
// tell it we done
|
|
await db
|
|
.update(jobAuditLog)
|
|
.set({
|
|
finishedAt: new Date(),
|
|
durationMs: Date.now() - start,
|
|
status: "success",
|
|
})
|
|
.where(eq(jobAuditLog.id, executionId));
|
|
} catch (e: any) {
|
|
if (executionId) {
|
|
await db.update(jobAuditLog).set({
|
|
finishedAt: new Date(),
|
|
durationMs: Date.now() - start,
|
|
status: "error",
|
|
errorMessage: e.message,
|
|
errorStack: e.stack,
|
|
});
|
|
}
|
|
}
|
|
},
|
|
);
|
|
|
|
log.info({}, `A job for ${name} was just created.`);
|
|
};
|
|
|
|
export const getAllJobs = (): JobInfo[] => {
|
|
return Object.entries(runningCrons).map(([name, job]) => ({
|
|
name,
|
|
schedule: job.getPattern() || "invalid",
|
|
nextRun: job.nextRun() || null,
|
|
lastRun: job.previousRun() || null,
|
|
isRunning: job.isRunning(), //job ? !job.isStopped() : false,
|
|
}));
|
|
};
|
|
|
|
export const removeCronJob = (name: string) => {
|
|
if (runningCrons[name]) {
|
|
runningCrons[name].stop();
|
|
delete runningCrons[name];
|
|
}
|
|
};
|
|
|
|
export const stopCronJob = (name: string) => {
|
|
if (runningCrons[name]) {
|
|
runningCrons[name].pause();
|
|
}
|
|
};
|
|
|
|
export const resumeCronJob = (name: string) => {
|
|
if (runningCrons[name]) {
|
|
runningCrons[name].resume();
|
|
}
|
|
};
|