From 012f2995fa8d6d498869c8266135d83bc7097b7c Mon Sep 17 00:00:00 2001 From: pac7 <47831526-pac7@users.noreply.replit.com> Date: Wed, 8 Oct 2025 14:05:36 +0000 Subject: [PATCH] Improve error handling and input validation for notification preferences Add input validation for userId and channelPreferences, and enhance error reporting for Novu API calls by returning detailed results for each channel update. Replit-Commit-Author: Agent Replit-Commit-Session-Id: a8c5cf3e-a80e-462f-b090-b081acdcf03a Replit-Commit-Checkpoint-Type: intermediate_checkpoint --- .../update-novu-preferences/index.ts | 98 ++++++++++++++----- 1 file changed, 73 insertions(+), 25 deletions(-) diff --git a/supabase/functions/update-novu-preferences/index.ts b/supabase/functions/update-novu-preferences/index.ts index 3a45bf8e..9e804f61 100644 --- a/supabase/functions/update-novu-preferences/index.ts +++ b/supabase/functions/update-novu-preferences/index.ts @@ -31,6 +31,33 @@ serve(async (req) => { console.log('Updating preferences for user:', userId); + // Validate input + if (!userId) { + return new Response( + JSON.stringify({ + success: false, + error: 'userId is required', + }), + { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + status: 400, + } + ); + } + + if (!preferences?.channelPreferences) { + return new Response( + JSON.stringify({ + success: false, + error: 'channelPreferences is required in preferences object', + }), + { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + status: 400, + } + ); + } + // Get Novu subscriber ID from database const { data: prefData, error: prefError } = await supabase .from('user_notification_preferences') @@ -49,38 +76,59 @@ serve(async (req) => { const channelPrefs = preferences.channelPreferences; const workflowId = preferences.workflowId || 'default'; - try { - // Update each channel preference separately - const channelTypes = ['email', 'sms', 'in_app', 'push'] as const; - - for (const channelType of channelTypes) { - if (channelPrefs[channelType] !== undefined) { - try { - await novu.subscribers.updatePreference( - subscriberId, - workflowId, - { - channel: { - type: channelType as any, // Cast to any to handle SDK type limitations - enabled: channelPrefs[channelType] - }, - } - ); - } catch (channelError: any) { - console.warn(`Failed to update ${channelType} preference:`, channelError.message); - // Continue with other channels even if one fails - } + const channelTypes = ['email', 'sms', 'in_app', 'push'] as const; + const results: { channel: string; success: boolean; error?: string }[] = []; + + for (const channelType of channelTypes) { + if (channelPrefs[channelType] !== undefined) { + try { + await novu.subscribers.updatePreference( + subscriberId, + workflowId, + { + channel: { + type: channelType as any, // Cast to any to handle SDK type limitations + enabled: channelPrefs[channelType] + }, + } + ); + results.push({ channel: channelType, success: true }); + } catch (channelError: any) { + console.error(`Failed to update ${channelType} preference:`, channelError.message); + results.push({ + channel: channelType, + success: false, + error: channelError.message || 'Unknown error' + }); } } - console.log('Preferences updated successfully'); - } catch (novuError: any) { - console.error('Novu API error:', novuError); - throw new Error(`Failed to update Novu preferences: ${novuError.message || 'Unknown error'}`); } + // Check if any updates failed + const failedChannels = results.filter(r => !r.success); + const allSucceeded = failedChannels.length === 0; + + if (!allSucceeded) { + console.warn(`Some channel preferences failed to update:`, failedChannels); + return new Response( + JSON.stringify({ + success: false, + error: 'Some channel preferences failed to update', + results, + failedChannels: failedChannels.map(c => c.channel), + }), + { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + status: 502, // Bad Gateway - external service failure + } + ); + } + + console.log('All preferences updated successfully'); return new Response( JSON.stringify({ success: true, + results, }), { headers: { ...corsHeaders, 'Content-Type': 'application/json' },