diff --git a/src/db/queries.ts b/src/db/queries.ts index e64022d..c3d6c61 100644 --- a/src/db/queries.ts +++ b/src/db/queries.ts @@ -69,11 +69,11 @@ export class VConQueries { } } - // Insert main vcon + // Upsert main vcon — idempotent on re-submission of same UUID // Set id = uuid so they match (id is the PK, uuid is the vCon document UUID) const { data: vconData, error: vconError } = await this.supabase .from('vcons') - .insert({ + .upsert({ id: vcon.uuid, // Explicitly set id to match uuid uuid: vcon.uuid, vcon_version: vcon.vcon ?? '0.3.0', @@ -85,7 +85,7 @@ export class VConQueries { redacted: vcon.redacted || {}, appended: vcon.appended || {}, // ✅ Added per spec tenant_id: tenantId, // ✅ Added for RLS multi-tenant support - }) + }, { onConflict: 'id' }) .select('id, uuid') .single(); @@ -97,6 +97,14 @@ export class VConQueries { throw vconError; } + // Delete existing child rows so re-submission replaces them cleanly + await Promise.all([ + this.supabase.from('parties').delete().eq('vcon_id', vconData.id), + this.supabase.from('dialog').delete().eq('vcon_id', vconData.id), + this.supabase.from('analysis').delete().eq('vcon_id', vconData.id), + this.supabase.from('attachments').delete().eq('vcon_id', vconData.id), + ]); + // Insert parties if (vcon.parties.length > 0) { const partiesData = vcon.parties.map((party, index) => ({ diff --git a/tests/db-queries.test.ts b/tests/db-queries.test.ts index 1378b93..10bbb11 100644 --- a/tests/db-queries.test.ts +++ b/tests/db-queries.test.ts @@ -19,6 +19,7 @@ describe('VConQueries', () => { from: vi.fn(), select: vi.fn(), insert: vi.fn(), + upsert: vi.fn(), update: vi.fn(), delete: vi.fn(), eq: vi.fn(), @@ -70,7 +71,7 @@ describe('VConQueries', () => { expect(result.uuid).toBe(testVCon.uuid); expect(mockSupabase.from).toHaveBeenCalledWith('vcons'); - expect(mockSupabase.insert).toHaveBeenCalled(); + expect(mockSupabase.upsert).toHaveBeenCalled(); }); it('should create vCon with all components (dialog, analysis, attachments)', async () => {