From 15b4441deb6bba5202f579f55f0307713e68441e Mon Sep 17 00:00:00 2001 From: chahinebrini Date: Thu, 18 Jun 2026 03:38:30 +0200 Subject: [PATCH] feat(backend): add MDM health check cron --- backend/server/plugins/mdm-health-cron.ts | 106 ++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 backend/server/plugins/mdm-health-cron.ts diff --git a/backend/server/plugins/mdm-health-cron.ts b/backend/server/plugins/mdm-health-cron.ts new file mode 100644 index 0000000..2939aab --- /dev/null +++ b/backend/server/plugins/mdm-health-cron.ts @@ -0,0 +1,106 @@ +/** + * MDM Healthcheck Cron + * + * Läuft alle 5 Minuten. Prüft für alle mit NanoMDM verknüpften iOS-Geräte + * den aktuellen Enrollment-/Supervision-Status und spiegelt ihn auf UserDevice. + */ +import { consola } from "consola"; +import { + getLinkedUserDevices, + getMdmEnrollmentStatusesByUdids, + updateUserDeviceMdmHealth, + type MdmEnrollmentStatus, +} from "../db/mdm"; + +const FIVE_MINUTES = 5 * 60 * 1000; +const INITIAL_DELAY_MS = 30 * 1000; + +let running = false; + +export default defineNitroPlugin((nitro) => { + if (import.meta.dev) { + consola.info("[mdm-health-cron] Skipping cron in dev mode"); + return; + } + + consola.info("[mdm-health-cron] Starting (5min interval)"); + + const initialTimer = setTimeout(() => { + runMdmHealthCheck().catch(() => {}); + }, INITIAL_DELAY_MS); + + const interval = setInterval(() => { + runMdmHealthCheck().catch(() => {}); + }, FIVE_MINUTES); + + nitro.hooks.hook("close", () => { + clearTimeout(initialTimer); + clearInterval(interval); + }); +}); + +async function runMdmHealthCheck() { + if (running) { + consola.info("[mdm-health-cron] Previous run still in progress, skipping"); + return; + } + + running = true; + const start = Date.now(); + + try { + const devices = await getLinkedUserDevices(); + if (devices.length === 0) { + consola.info("[mdm-health-cron] No linked iOS devices"); + return; + } + + const statuses = await getMdmEnrollmentStatusesByUdids( + devices.map((d) => d.mdmId).filter((id): id is string => id != null), + ); + + let updated = 0; + let unchanged = 0; + + for (const device of devices) { + const status: MdmEnrollmentStatus = statuses.get(device.mdmId ?? "") ?? { + enrolled: false, + supervised: false, + lastSeenAt: null, + }; + + const changed = + device.mdmEnrolled !== status.enrolled || + device.mdmSupervised !== status.supervised || + !sameNullableDate(device.mdmLastSeenAt, status.lastSeenAt); + + if (!changed) { + unchanged++; + continue; + } + + try { + await updateUserDeviceMdmHealth(device.id, status); + updated++; + } catch (err: any) { + consola.error( + `[mdm-health-cron] Failed to update device ${device.id}: ${err?.message ?? err}`, + ); + } + } + + consola.success( + `[mdm-health-cron] Checked ${devices.length} devices in ${Date.now() - start}ms (${updated} updated, ${unchanged} unchanged)`, + ); + } catch (err: any) { + consola.error("[mdm-health-cron] run failed:", err?.message ?? err); + } finally { + running = false; + } +} + +function sameNullableDate(a: Date | null, b: Date | null): boolean { + if (a === null && b === null) return true; + if (a === null || b === null) return false; + return a.getTime() === b.getTime(); +}