diff --git a/supabase/functions/create-novu-subscriber/index.ts b/supabase/functions/create-novu-subscriber/index.ts index 595ae517..84d9894b 100644 --- a/supabase/functions/create-novu-subscriber/index.ts +++ b/supabase/functions/create-novu-subscriber/index.ts @@ -203,6 +203,18 @@ serve(async (req) => { const duration = endRequest(tracking); console.log('Subscriber created successfully:', subscriber.data, { requestId: tracking.requestId, duration }); + // Add subscriber to "users" topic for global announcements + try { + console.log('Adding subscriber to "users" topic...', { subscriberId }); + await novu.topics.addSubscribers('users', { + subscribers: [subscriberId], + }); + console.log('Successfully added subscriber to "users" topic', { subscriberId }); + } catch (topicError: any) { + // Non-blocking - log error but don't fail the request + console.error('Failed to add subscriber to "users" topic:', topicError.message, { subscriberId }); + } + return new Response( JSON.stringify({ success: true, diff --git a/supabase/functions/process-scheduled-deletions/index.ts b/supabase/functions/process-scheduled-deletions/index.ts index 24516d1f..1c25fd83 100644 --- a/supabase/functions/process-scheduled-deletions/index.ts +++ b/supabase/functions/process-scheduled-deletions/index.ts @@ -128,6 +128,30 @@ serve(async (req) => { .delete() .eq('user_id', deletion.user_id); + // Remove from Novu before deleting auth user + try { + console.log(`Removing Novu subscriber: ${deletion.user_id}`); + + const { error: novuError } = await supabaseAdmin.functions.invoke( + 'remove-novu-subscriber', + { + body: { + subscriberId: deletion.user_id, + deleteSubscriber: true // Also delete the subscriber entirely + } + } + ); + + if (novuError) { + console.error('Failed to remove Novu subscriber:', novuError); + } else { + console.log('Novu subscriber removed successfully'); + } + } catch (novuError) { + // Non-blocking - log but continue with deletion + console.error('Error removing Novu subscriber:', novuError); + } + // Update deletion request status await supabaseAdmin .from('account_deletion_requests') diff --git a/supabase/functions/remove-novu-subscriber/index.ts b/supabase/functions/remove-novu-subscriber/index.ts new file mode 100644 index 00000000..46529939 --- /dev/null +++ b/supabase/functions/remove-novu-subscriber/index.ts @@ -0,0 +1,112 @@ +import { serve } from "https://deno.land/std@0.190.0/http/server.ts"; +import { Novu } from "npm:@novu/api@1.6.0"; +import { startRequest, endRequest } from "../_shared/logger.ts"; + +const corsHeaders = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, x-request-id', +}; + +serve(async (req) => { + if (req.method === 'OPTIONS') { + return new Response(null, { headers: corsHeaders }); + } + + const tracking = startRequest('remove-novu-subscriber'); + + try { + const novuApiKey = Deno.env.get('NOVU_API_KEY'); + + if (!novuApiKey) { + throw new Error('NOVU_API_KEY is not configured'); + } + + const novu = new Novu({ + secretKey: novuApiKey + }); + + const { subscriberId, deleteSubscriber = false } = await req.json() as { + subscriberId: string; + deleteSubscriber?: boolean; + }; + + if (!subscriberId || typeof subscriberId !== 'string' || subscriberId.trim() === '') { + return new Response( + JSON.stringify({ + success: false, + error: 'subscriberId is required and must be a non-empty string', + }), + { + headers: { ...corsHeaders, 'Content-Type': 'application/json' }, + status: 400, + } + ); + } + + console.log('Removing subscriber from "users" topic:', { subscriberId, requestId: tracking.requestId }); + + // Remove subscriber from "users" topic + try { + await novu.topics.removeSubscribers('users', { + subscribers: [subscriberId], + }); + console.log('Successfully removed subscriber from "users" topic', { subscriberId }); + } catch (topicError: any) { + console.error('Failed to remove subscriber from "users" topic:', topicError.message, { subscriberId }); + // Continue - we still want to delete the subscriber if requested + } + + // Optionally delete the subscriber entirely from Novu + if (deleteSubscriber) { + try { + console.log('Deleting subscriber from Novu:', { subscriberId }); + await novu.subscribers.delete(subscriberId); + console.log('Successfully deleted subscriber from Novu', { subscriberId }); + } catch (deleteError: any) { + console.error('Failed to delete subscriber from Novu:', deleteError.message, { subscriberId }); + throw deleteError; + } + } + + endRequest(tracking, 200); + + return new Response( + JSON.stringify({ + success: true, + subscriberId, + removedFromTopic: true, + deleted: deleteSubscriber, + requestId: tracking.requestId + }), + { + headers: { + ...corsHeaders, + 'Content-Type': 'application/json', + 'X-Request-ID': tracking.requestId + }, + status: 200, + } + ); + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; + console.error('Error removing Novu subscriber:', errorMessage, { requestId: tracking.requestId }); + + endRequest(tracking, 500, errorMessage); + + return new Response( + JSON.stringify({ + success: false, + error: errorMessage, + requestId: tracking.requestId + }), + { + headers: { + ...corsHeaders, + 'Content-Type': 'application/json', + 'X-Request-ID': tracking.requestId + }, + status: 500, + } + ); + } +});