/** * Helper functions for relational audit logging * Replaces JSONB storage with proper relational tables */ import { supabase } from '@/lib/supabaseClient'; import { handleNonCriticalError } from './errorHandler'; /** * Write admin audit details to relational table * Replaces JSONB admin_audit_log.details column */ export async function writeAdminAuditDetails( auditLogId: string, details: Record ): Promise { if (!details || Object.keys(details).length === 0) return; const entries = Object.entries(details).map(([key, value]) => ({ audit_log_id: auditLogId, detail_key: key, detail_value: typeof value === 'object' ? JSON.stringify(value) : String(value), })); const { error } = await supabase .from('admin_audit_details') .insert(entries); if (error) { handleNonCriticalError(error, { action: 'Write admin audit details', metadata: { auditLogId }, }); throw error; } } /** * Write moderation audit metadata to relational table * Replaces JSONB moderation_audit_log.metadata column */ export async function writeModerationAuditMetadata( auditLogId: string, metadata: Record ): Promise { if (!metadata || Object.keys(metadata).length === 0) return; const entries = Object.entries(metadata).map(([key, value]) => ({ audit_log_id: auditLogId, metadata_key: key, metadata_value: typeof value === 'object' ? JSON.stringify(value) : String(value), })); const { error } = await supabase .from('moderation_audit_metadata') .insert(entries); if (error) { handleNonCriticalError(error, { action: 'Write moderation audit metadata', metadata: { auditLogId }, }); throw error; } } /** * Write item change fields to relational table * Replaces JSONB item_edit_history.changes column */ export async function writeItemChangeFields( editHistoryId: string, changes: Record ): Promise { if (!changes || Object.keys(changes).length === 0) return; const entries = Object.entries(changes).map(([fieldName, change]) => ({ edit_history_id: editHistoryId, field_name: fieldName, old_value: change.old_value !== undefined ? (typeof change.old_value === 'object' ? JSON.stringify(change.old_value) : String(change.old_value)) : null, new_value: change.new_value !== undefined ? (typeof change.new_value === 'object' ? JSON.stringify(change.new_value) : String(change.new_value)) : null, })); const { error } = await supabase .from('item_change_fields') .insert(entries); if (error) { handleNonCriticalError(error, { action: 'Write item change fields', metadata: { editHistoryId }, }); throw error; } } /** * Write request breadcrumbs to relational table * Replaces JSONB request_metadata.breadcrumbs column */ export async function writeRequestBreadcrumbs( requestId: string, breadcrumbs: Array<{ timestamp: string; category: string; message: string; level?: 'debug' | 'info' | 'warn' | 'error'; }> ): Promise { if (!breadcrumbs || breadcrumbs.length === 0) return; const entries = breadcrumbs.map((breadcrumb, index) => ({ request_id: requestId, timestamp: breadcrumb.timestamp, category: breadcrumb.category, message: breadcrumb.message, level: breadcrumb.level || 'info', sequence_order: index, })); const { error } = await supabase .from('request_breadcrumbs') .insert(entries); if (error) { handleNonCriticalError(error, { action: 'Write request breadcrumbs', metadata: { requestId }, }); throw error; } } /** * Read admin audit details from relational table */ export async function readAdminAuditDetails( auditLogId: string ): Promise> { const { data, error } = await supabase .from('admin_audit_details') .select('detail_key, detail_value') .eq('audit_log_id', auditLogId); if (error) { handleNonCriticalError(error, { action: 'Read admin audit details', metadata: { auditLogId }, }); return {}; } return data.reduce((acc, row) => { acc[row.detail_key] = row.detail_value; return acc; }, {} as Record); } /** * Read moderation audit metadata from relational table */ export async function readModerationAuditMetadata( auditLogId: string ): Promise> { const { data, error } = await supabase .from('moderation_audit_metadata') .select('metadata_key, metadata_value') .eq('audit_log_id', auditLogId); if (error) { handleNonCriticalError(error, { action: 'Read moderation audit metadata', metadata: { auditLogId }, }); return {}; } return data.reduce((acc, row) => { acc[row.metadata_key] = row.metadata_value; return acc; }, {} as Record); } /** * Read item change fields from relational table */ export async function readItemChangeFields( editHistoryId: string ): Promise> { const { data, error } = await supabase .from('item_change_fields') .select('field_name, old_value, new_value') .eq('edit_history_id', editHistoryId); if (error) { handleNonCriticalError(error, { action: 'Read item change fields', metadata: { editHistoryId }, }); return {}; } return data.reduce((acc, row) => { acc[row.field_name] = { old_value: row.old_value, new_value: row.new_value, }; return acc; }, {} as Record); } /** * Write profile change fields to relational table * Replaces JSONB profile_audit_log.changes column */ export async function writeProfileChangeFields( auditLogId: string, changes: Record ): Promise { if (!changes || Object.keys(changes).length === 0) return; const entries = Object.entries(changes).map(([fieldName, change]) => ({ audit_log_id: auditLogId, field_name: fieldName, old_value: change.old_value !== undefined ? (typeof change.old_value === 'object' ? JSON.stringify(change.old_value) : String(change.old_value)) : null, new_value: change.new_value !== undefined ? (typeof change.new_value === 'object' ? JSON.stringify(change.new_value) : String(change.new_value)) : null, })); const { error } = await supabase .from('profile_change_fields') .insert(entries); if (error) { handleNonCriticalError(error, { action: 'Write profile change fields', metadata: { auditLogId }, }); throw error; } } /** * Write conflict detail fields to relational table * Replaces JSONB conflict_resolutions.conflict_details column */ export async function writeConflictDetailFields( conflictResolutionId: string, conflictData: Record ): Promise { if (!conflictData || Object.keys(conflictData).length === 0) return; const entries = Object.entries(conflictData).map(([fieldName, value]) => ({ conflict_resolution_id: conflictResolutionId, field_name: fieldName, conflicting_value_1: typeof value === 'object' && value !== null && 'v1' in value ? String((value as any).v1) : null, conflicting_value_2: typeof value === 'object' && value !== null && 'v2' in value ? String((value as any).v2) : null, resolved_value: typeof value === 'object' && value !== null && 'resolved' in value ? String((value as any).resolved) : null, })); const { error } = await supabase .from('conflict_detail_fields') .insert(entries); if (error) { handleNonCriticalError(error, { action: 'Write conflict detail fields', metadata: { conflictResolutionId }, }); throw error; } }