feat(backend): add bulk MDM health status helpers

Add MdmEnrollmentStatus, UserDeviceMdmHealthRecord types and helpers:
- getLinkedUserDevices() to load iOS devices with NanoMDM UDIDs
- getMdmEnrollmentStatusesByUdids() for bulk NanoMDM lookups
- updateUserDeviceMdmHealth() to mirror status on UserDevice

Also fix PoolConfig option casing: queryTimeout -> query_timeout.
This commit is contained in:
chahinebrini 2026-06-18 03:33:20 +02:00
parent b107262d60
commit 74784fc4da

View File

@ -24,7 +24,7 @@ function useMdmPool(): pg.Pool {
// NanoMDM queries are point lookups — keep pool small. // NanoMDM queries are point lookups — keep pool small.
max: 5, max: 5,
connectionTimeoutMillis: 5000, connectionTimeoutMillis: 5000,
queryTimeout: 5000, query_timeout: 5000,
}); });
return _mdmPool; return _mdmPool;
@ -46,6 +46,17 @@ const USER_DEVICE_MDM_SELECT = {
mdmId: true, mdmId: true,
} as const; } as const;
const USER_DEVICE_MDM_HEALTH_SELECT = {
id: true,
userId: true,
deviceId: true,
platform: true,
mdmId: true,
mdmEnrolled: true,
mdmSupervised: true,
mdmLastSeenAt: true,
} as const;
/** /**
* Find a user's iOS device by Capacitor deviceId. * Find a user's iOS device by Capacitor deviceId.
*/ */
@ -90,6 +101,17 @@ export async function clearUserDeviceMdmId(
}); });
} }
/**
* Load all iOS devices that have a NanoMDM UDID link.
*/
export async function getLinkedUserDevices(): Promise<UserDeviceMdmHealthRecord[]> {
const db = usePrisma();
return db.userDevice.findMany({
where: { platform: "ios", mdmId: { not: null } },
select: USER_DEVICE_MDM_HEALTH_SELECT,
});
}
export interface MdmDeviceStatus { export interface MdmDeviceStatus {
enrolled: boolean; enrolled: boolean;
company: string | null; company: string | null;
@ -99,6 +121,23 @@ export interface MdmDeviceStatus {
lastAppPushAt: Date | null; lastAppPushAt: Date | null;
} }
export interface MdmEnrollmentStatus {
enrolled: boolean;
supervised: boolean;
lastSeenAt: Date | null;
}
export interface UserDeviceMdmHealthRecord {
id: string;
userId: string;
deviceId: string;
platform: string;
mdmId: string | null;
mdmEnrolled: boolean | null;
mdmSupervised: boolean | null;
mdmLastSeenAt: Date | null;
}
/** /**
* Query NanoMDM Postgres for a device by UDID. * Query NanoMDM Postgres for a device by UDID.
* *
@ -144,3 +183,64 @@ export async function getMdmStatusByUdid(
lastAppPushAt: row?.last_app_push_at ?? null, lastAppPushAt: row?.last_app_push_at ?? null,
}; };
} }
/**
* Bulk-query NanoMDM for enrollment/supervision/last-seen status.
* Returns a map keyed by UDID. Missing devices are omitted.
*
* Throws if the MDM DB is unreachable callers should treat this as an
* infra/runtime error and not cache a negative result.
*/
export async function getMdmEnrollmentStatusesByUdids(
udids: string[],
): Promise<Map<string, MdmEnrollmentStatus>> {
if (udids.length === 0) {
return new Map();
}
const pool = useMdmPool();
const result = await pool.query<{
udid: string;
enrolled: boolean;
supervised: boolean;
last_seen_at: Date | null;
}>(
`SELECT
d.id AS udid,
COALESCE(e.enabled = TRUE, FALSE) AS enrolled,
(d.unlock_token IS NOT NULL) AS supervised,
e.last_seen_at
FROM devices d
LEFT JOIN enrollments e ON e.device_id = d.id
WHERE d.id = ANY($1::text[])`,
[udids],
);
const map = new Map<string, MdmEnrollmentStatus>();
for (const row of result.rows) {
map.set(row.udid, {
enrolled: row.enrolled,
supervised: row.supervised,
lastSeenAt: row.last_seen_at,
});
}
return map;
}
/**
* Persist mirrored MDM health status on a UserDevice row.
*/
export async function updateUserDeviceMdmHealth(
id: string,
status: MdmEnrollmentStatus,
): Promise<void> {
const db = usePrisma();
await db.userDevice.update({
where: { id },
data: {
mdmEnrolled: status.enrolled,
mdmSupervised: status.supervised,
mdmLastSeenAt: status.lastSeenAt,
},
});
}