Files
EpicNext-Cms/scripts/jobs-worker.ts
T
SimoandCursor 09f1bc2bd6
Local Build and Deploy / deploy (push) Successful in 1m9s
Add production observability: Sentry, pino, and sharp badge encoding.
Sentry is opt-in via DSN env vars; logger uses structured pino JSON in prod; badge uploads are normalized to GIF with sharp.

Co-authored-by: Cursor <[email protected]>
2026-07-17 23:09:57 +02:00

124 lines
3.2 KiB
TypeScript

import { Cron } from "croner";
import { env } from "../src/env";
import { logger } from "../src/lib/logger";
import { prisma } from "../src/lib/prisma";
async function backupEmulatorJar(): Promise<void> {
if (!env.EMULATOR_JAR_PATH || !env.EMULATOR_BACKUP_DIR) return;
const { copyFileSync, mkdirSync, readdirSync, unlinkSync, existsSync } =
await import("node:fs");
const { resolve } = await import("node:path");
const timestamp = new Date().toISOString().slice(0, 19).replace(/[T:]/g, "-");
const backupFile = resolve(
env.EMULATOR_BACKUP_DIR,
`emulator-${timestamp}.jar`,
);
if (!existsSync(env.EMULATOR_BACKUP_DIR)) {
mkdirSync(env.EMULATOR_BACKUP_DIR, { recursive: true });
}
try {
copyFileSync(env.EMULATOR_JAR_PATH, backupFile);
logger.info("Backed up emulator JAR", {
module: "jobs",
backupFile,
});
const keep = env.EMULATOR_BACKUP_KEEP ?? 7;
const files = readdirSync(env.EMULATOR_BACKUP_DIR)
.filter((f) => f.startsWith("emulator-") && f.endsWith(".jar"))
.sort()
.reverse();
for (let i = keep; i < files.length; i++) {
unlinkSync(resolve(env.EMULATOR_BACKUP_DIR, files[i]));
logger.info("Rotated out old backup", {
module: "jobs",
file: files[i],
});
}
} catch (err) {
logger.error("JAR backup failed", {
module: "jobs",
err: err instanceof Error ? err.message : String(err),
});
}
}
async function cleanupOldLogs(): Promise<void> {
try {
const cutoff = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
await prisma.websiteLoginLogs.deleteMany({
where: { createdAt: { lt: cutoff } },
});
logger.info("Cleaned up login logs older than 30 days", { module: "jobs" });
} catch (err) {
logger.error("Log cleanup failed", {
module: "jobs",
err: err instanceof Error ? err.message : String(err),
});
}
}
async function cleanupOldSessions(): Promise<void> {
try {
const cutoff = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
await prisma.passwordReset.deleteMany({
where: { createdAt: { lt: cutoff } },
});
logger.info("Cleaned up expired password reset tokens", {
module: "jobs",
});
} catch (err) {
logger.error("Session cleanup failed", {
module: "jobs",
err: err instanceof Error ? err.message : String(err),
});
}
}
async function main() {
logger.info("Worker started", { module: "jobs" });
if (env.EMULATOR_JAR_PATH && env.EMULATOR_BACKUP_DIR) {
new Cron("0 3 * * *", () => {
backupEmulatorJar().catch((e) =>
logger.error("Backup error", {
module: "jobs",
err: e instanceof Error ? e.message : String(e),
}),
);
});
logger.info("Scheduled: emulator JAR backup (daily 03:00)", {
module: "jobs",
});
}
new Cron("0 4 * * *", () => {
Promise.all([cleanupOldLogs(), cleanupOldSessions()]).catch((e) =>
logger.error("Cleanup error", {
module: "jobs",
err: e instanceof Error ? e.message : String(e),
}),
);
});
logger.info("Scheduled: old data cleanup (daily 04:00)", { module: "jobs" });
await Promise.all([
backupEmulatorJar(),
cleanupOldLogs(),
cleanupOldSessions(),
]);
}
main().catch((err) => {
logger.error("Fatal", {
module: "jobs",
err: err instanceof Error ? err.message : String(err),
});
process.exit(1);
});