reafactored data mart and added better job monitor

This commit is contained in:
2026-02-19 13:20:20 -06:00
parent 76503f558b
commit 597d990a69
29 changed files with 2857 additions and 621 deletions

View File

@@ -1,4 +1,7 @@
import { jobAuditLog } from "backend/db/schema/auditLog.schema.js";
import { Cron } from "croner";
import { eq } from "drizzle-orm";
import { db } from "../db/db.controller.js";
import { createLogger } from "../logger/logger.controller.js";
// example createJob
@@ -16,15 +19,22 @@ export interface JobInfo {
// Store running cronjobs
export const runningCrons: Record<string, Cron> = {};
/**
*
* @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
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 exists
// Destroy existing job if it exist
if (runningCrons[name]) {
runningCrons[name].stop();
}
@@ -37,10 +47,48 @@ export const createCronJob = async (
catch: true, // Prevents unhandled rejections
name: name,
},
task,
);
async () => {
const startedAt = new Date();
const start = Date.now();
const log = createLogger({ module: "system", subModule: "croner" });
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.`);
};

View File

@@ -0,0 +1,19 @@
import { Router } from "express";
import { apiReturn } from "../utils/returnHelper.utils.js";
import { getAllJobs } from "./croner.utils.js";
const r = Router();
r.get("/", async (_, res) => {
return apiReturn(res, {
success: true,
level: "info",
module: "utils",
subModule: "jobs",
message: "All current Jobs",
data: getAllJobs(),
status: 200,
});
});
export default r;

View File

@@ -0,0 +1,63 @@
import { Router } from "express";
import { apiReturn } from "../utils/returnHelper.utils.js";
import { getAllJobs, resumeCronJob, stopCronJob } from "./croner.utils.js";
const r = Router();
r.patch("/:status", async (req, res) => {
const { status } = req.params;
const body = req.body;
if (!body.name) {
return apiReturn(res, {
success: false,
level: "error",
module: "utils",
subModule: "jobs",
message: "Missing manadatory name ",
data: getAllJobs(),
status: 400,
});
}
const statusCheck = ["start", "stop"];
if (!statusCheck.includes(status)) {
return apiReturn(res, {
success: false,
level: "error",
module: "utils",
subModule: "jobs",
message: "You have passed an invalid option please try again. ",
data: getAllJobs(),
status: 400,
});
}
if (status === "start") {
resumeCronJob(body.name);
return apiReturn(res, {
success: true,
level: "info",
module: "utils",
subModule: "jobs",
message: `${name} was restarted`,
data: getAllJobs(),
status: 200,
});
}
if (status === "stop") {
stopCronJob(body.name);
return apiReturn(res, {
success: true,
level: "info",
module: "utils",
subModule: "jobs",
message: `${body.name} was stopped`,
data: getAllJobs(),
status: 200,
});
}
});
export default r;

View File

@@ -0,0 +1,3 @@
export const delay = (ms: number) => {
return new Promise((resolve) => setTimeout(resolve, ms));
};

View File

@@ -1,16 +1,8 @@
import type { Express } from "express";
import { getAllJobs } from "./croner.utils.js";
import { apiReturn } from "./returnHelper.utils.js";
import getActiveJobs from "./cronnerActiveJobs.route.js";
import jobStatusChange from "./cronnerStatusChange.js";
export const setupUtilsRoutes = (baseUrl: string, app: Express) => {
app.get(`${baseUrl}/api/utils`, (_, res) => {
return apiReturn(res, {
success: true,
level: "info",
module: "utils",
subModule: "jobs",
message: "All current Jobs",
data: getAllJobs(),
status: 200,
});
});
app.use(`${baseUrl}/api/utils/croner`, getActiveJobs);
app.use(`${baseUrl}/api/utils/croner`, jobStatusChange);
};