tenantpilot/worker/jobs/dbUpsert.ts

74 lines
2.1 KiB
TypeScript

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;