128 lines
3.4 KiB
TypeScript
128 lines
3.4 KiB
TypeScript
import * as Sentry from "@sentry/nextjs";
|
|
import { Cron } from "croner";
|
|
import { lt } from "drizzle-orm";
|
|
import { env } from "../src/env";
|
|
import { db, PasswordReset, WebsiteLoginLogs } from "../src/lib/db";
|
|
import { logger } from "../src/lib/logger";
|
|
|
|
function initWorkerSentry(): void {
|
|
const dsn = process.env.SENTRY_DSN;
|
|
if (!dsn || process.env.NODE_ENV !== "production") return;
|
|
|
|
Sentry.init({
|
|
dsn,
|
|
environment: process.env.NODE_ENV,
|
|
release: process.env.APP_VERSION,
|
|
tracesSampleRate: 0.05,
|
|
});
|
|
logger.info("Sentry initialized for jobs worker", { module: "jobs" });
|
|
}
|
|
|
|
function captureWorkerError(err: unknown, context: string): void {
|
|
logger.error(context, {
|
|
module: "jobs",
|
|
err: err instanceof Error ? err.message : String(err),
|
|
});
|
|
if (process.env.SENTRY_DSN) {
|
|
Sentry.captureException(err);
|
|
}
|
|
}
|
|
|
|
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) {
|
|
captureWorkerError(err, "JAR backup failed");
|
|
}
|
|
}
|
|
|
|
async function cleanupOldLogs(): Promise<void> {
|
|
try {
|
|
const cutoff = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
|
|
await db
|
|
.delete(WebsiteLoginLogs)
|
|
.where(lt(WebsiteLoginLogs.createdAt, cutoff));
|
|
logger.info("Cleaned up login logs older than 30 days", { module: "jobs" });
|
|
} catch (err) {
|
|
captureWorkerError(err, "Log cleanup failed");
|
|
}
|
|
}
|
|
|
|
async function cleanupOldSessions(): Promise<void> {
|
|
try {
|
|
const cutoff = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000);
|
|
await db.delete(PasswordReset).where(lt(PasswordReset.createdAt, cutoff));
|
|
logger.info("Cleaned up expired password reset tokens", {
|
|
module: "jobs",
|
|
});
|
|
} catch (err) {
|
|
captureWorkerError(err, "Session cleanup failed");
|
|
}
|
|
}
|
|
|
|
async function main() {
|
|
initWorkerSentry();
|
|
logger.info("Worker started", { module: "jobs" });
|
|
|
|
if (env.EMULATOR_JAR_PATH && env.EMULATOR_BACKUP_DIR) {
|
|
new Cron("0 3 * * *", () => {
|
|
backupEmulatorJar().catch((e) => captureWorkerError(e, "Backup error"));
|
|
});
|
|
logger.info("Scheduled: emulator JAR backup (daily 03:00)", {
|
|
module: "jobs",
|
|
});
|
|
}
|
|
|
|
new Cron("0 4 * * *", () => {
|
|
Promise.all([cleanupOldLogs(), cleanupOldSessions()]).catch((e) =>
|
|
captureWorkerError(e, "Cleanup error"),
|
|
);
|
|
});
|
|
logger.info("Scheduled: old data cleanup (daily 04:00)", { module: "jobs" });
|
|
|
|
await Promise.all([
|
|
backupEmulatorJar(),
|
|
cleanupOldLogs(),
|
|
cleanupOldSessions(),
|
|
]);
|
|
}
|
|
|
|
main().catch((err) => {
|
|
captureWorkerError(err, "Fatal");
|
|
process.exit(1);
|
|
});
|