import { db } from '../../lib/db'; import { policySettings } from '../../lib/db/schema/policySettings'; import type { NewPolicySetting } from '../../lib/db/schema/policySettings'; import type { FlattenedSetting } from './policyParser'; import logger from '../logging'; /** * Upsert policy settings to database with conflict resolution */ export async function upsertPolicySettings( tenantId: string, settings: FlattenedSetting[] ): Promise<{ inserted: number; updated: number }> { if (settings.length === 0) { logger.info({ event: 'dbUpsert:skip', reason: 'no settings to upsert' }); return { inserted: 0, updated: 0 }; } const now = new Date(); // Convert to database insert format const records: NewPolicySetting[] = settings.map((setting) => ({ tenantId, policyName: setting.policyName, policyType: setting.policyType, settingName: setting.settingName, settingValue: setting.settingValue, graphPolicyId: setting.graphPolicyId, lastSyncedAt: now, })); try { // Batch upsert with conflict resolution // Uses the unique constraint: (tenantId, graphPolicyId, settingName) const result = await db .insert(policySettings) .values(records) .onConflictDoUpdate({ target: [ policySettings.tenantId, policySettings.graphPolicyId, policySettings.settingName, ], set: { policyName: policySettings.policyName, policyType: policySettings.policyType, settingValue: policySettings.settingValue, lastSyncedAt: now, }, }); // Drizzle doesn't return row counts in all cases, so we estimate const total = records.length; logger.info({ event: 'dbUpsert:success', total, tenantId, policies: [...new Set(settings.map(s => s.graphPolicyId))].length }); return { inserted: total, updated: 0 }; } catch (error) { logger.error({ event: 'dbUpsert:error', error: error instanceof Error ? error.message : String(error), tenantId, settingsCount: settings.length }); throw error; } } export default upsertPolicySettings;