import { usePrisma } from "../utils/prisma"; import pg from "pg"; const { Pool } = pg; let _mdmPool: pg.Pool | null = null; /** * Lazily initialised pg.Pool against the NanoMDM Postgres. * Connection string comes from runtimeConfig.mdmDatabaseUrl. */ function useMdmPool(): pg.Pool { if (_mdmPool) return _mdmPool; const config = useRuntimeConfig(); const connectionString = (config as any).mdmDatabaseUrl; if (!connectionString) { throw new Error("MDM_DATABASE_URL not configured"); } _mdmPool = new Pool({ connectionString, // NanoMDM queries are point lookups — keep pool small. max: 5, connectionTimeoutMillis: 5000, query_timeout: 5000, }); return _mdmPool; } export interface UserDeviceMdmRecord { id: string; userId: string; deviceId: string; platform: string; mdmId: string | null; } const USER_DEVICE_MDM_SELECT = { id: true, userId: true, deviceId: true, platform: true, mdmId: true, } 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. */ export async function getUserDeviceByDeviceId( userId: string, deviceId: string, platform: string = "ios", ): Promise { const db = usePrisma(); return db.userDevice.findFirst({ where: { userId, deviceId, platform }, select: USER_DEVICE_MDM_SELECT, }); } /** * Persist the NanoMDM UDID for a user's device. */ export async function setUserDeviceMdmId( userId: string, deviceId: string, mdmId: string, ): Promise { const db = usePrisma(); await db.userDevice.updateMany({ where: { userId, deviceId, platform: "ios" }, data: { mdmId }, }); } /** * Clear the stored NanoMDM UDID (e.g. device no longer enrolled). */ export async function clearUserDeviceMdmId( userId: string, deviceId: string, ): Promise { const db = usePrisma(); await db.userDevice.updateMany({ where: { userId, deviceId, platform: "ios" }, data: { mdmId: null }, }); } /** * 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 { enrolled: boolean; exists: boolean; company: string | null; supervised: boolean; tokenUpdateAt: Date | null; lastAckAt: 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. * * 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 getMdmStatusByUdid( udid: string, ): Promise { const pool = useMdmPool(); // Defensive: only raw parameters reach the query layer below. const result = await pool.query<{ unlock_token: Buffer | null; token_update_at: Date | null; last_ack: Date | null; last_app_push_at: Date | null; enrolled: boolean; }>( `SELECT d.unlock_token, d.token_update_at, COALESCE(e.enabled = TRUE, FALSE) AS enrolled, (SELECT max(updated_at) FROM command_results WHERE id = d.id) AS last_ack, (SELECT max(r.updated_at) FROM command_results r JOIN commands c ON c.command_uuid = r.command_uuid WHERE r.id = d.id AND c.request_type = 'InstallApplication' AND r.status = 'Acknowledged') AS last_app_push_at FROM devices d LEFT JOIN enrollments e ON e.device_id = d.id WHERE d.id = $1`, [udid], ); const row = result.rows[0]; const exists = row !== undefined; const enrolled = row?.enrolled ?? false; return { enrolled, exists, company: enrolled ? "ReBreak" : null, supervised: exists && row?.unlock_token != null, tokenUpdateAt: row?.token_update_at ?? null, lastAckAt: row?.last_ack ?? 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> { 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(); 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 { const db = usePrisma(); await db.userDevice.update({ where: { id }, data: { mdmEnrolled: status.enrolled, mdmSupervised: status.supervised, mdmLastSeenAt: status.lastSeenAt, }, }); }