Files
thrilltrack-explorer/supabase/functions/update-novu-preferences/index.ts
pac7 012f2995fa 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
2025-10-08 14:05:36 +00:00

153 lines
4.5 KiB
TypeScript

import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
import { Novu } from "npm:@novu/node@2.0.2";
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4";
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
};
serve(async (req) => {
if (req.method === 'OPTIONS') {
return new Response(null, { headers: corsHeaders });
}
try {
const novuApiKey = Deno.env.get('NOVU_API_KEY');
const supabaseUrl = Deno.env.get('SUPABASE_URL')!;
const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!;
if (!novuApiKey) {
throw new Error('NOVU_API_KEY is not configured');
}
const novu = new Novu(novuApiKey, {
backendUrl: Deno.env.get('VITE_NOVU_API_URL') || 'https://api.novu.co',
});
const supabase = createClient(supabaseUrl, supabaseServiceKey);
const { userId, preferences } = await req.json();
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')
.select('novu_subscriber_id')
.eq('user_id', userId)
.single();
if (prefError || !prefData?.novu_subscriber_id) {
throw new Error('Novu subscriber not found');
}
const subscriberId = prefData.novu_subscriber_id;
// Update channel preferences in Novu
// Note: Novu's updatePreference updates one channel at a time for a specific workflow
const channelPrefs = preferences.channelPreferences;
const workflowId = preferences.workflowId || 'default';
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'
});
}
}
}
// 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' },
status: 200,
}
);
} catch (error: any) {
console.error('Error updating Novu preferences:', error);
return new Response(
JSON.stringify({
success: false,
error: error.message,
}),
{
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
status: 500,
}
);
}
});