Files
thrilltrack-explorer/supabase/functions/novu-webhook/index.ts
gpt-engineer-app[bot] bf3da6414a Centralize CORS configuration
Consolidate CORS handling by introducing a shared supabase/functions/_shared/cors.ts and migrate edge functions to import from it. Remove inline cors.ts usage across functions, standardize headers (including traceparent and x-request-id), and prepare for environment-aware origins.
2025-11-10 21:28:46 +00:00

135 lines
3.7 KiB
TypeScript

import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4";
import { corsHeaders } from '../_shared/cors.ts';
import { edgeLogger } from '../_shared/logger.ts';
// Simple request tracking
const startRequest = () => ({ requestId: crypto.randomUUID(), start: Date.now() });
const endRequest = (tracking: { start: number }) => Date.now() - tracking.start;
serve(async (req) => {
const tracking = startRequest();
if (req.method === 'OPTIONS') {
return new Response(null, { headers: corsHeaders });
}
try {
const supabaseUrl = Deno.env.get('SUPABASE_URL')!;
const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!;
const supabase = createClient(supabaseUrl, supabaseServiceKey);
const event = await req.json();
edgeLogger.info('Received Novu webhook event', {
action: 'novu_webhook',
eventType: event.type,
requestId: tracking.requestId
});
// Handle different webhook events
switch (event.type) {
case 'notification.sent':
await handleNotificationSent(supabase, event);
break;
case 'notification.delivered':
await handleNotificationDelivered(supabase, event);
break;
case 'notification.read':
await handleNotificationRead(supabase, event);
break;
case 'notification.failed':
await handleNotificationFailed(supabase, event);
break;
default:
edgeLogger.warn('Unhandled Novu event type', {
action: 'novu_webhook',
eventType: event.type,
requestId: tracking.requestId
});
}
const duration = endRequest(tracking);
return new Response(
JSON.stringify({ success: true, requestId: tracking.requestId }),
{
headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
},
status: 200,
}
);
} catch (error: any) {
const duration = endRequest(tracking);
edgeLogger.error('Error processing webhook', {
action: 'novu_webhook',
error: error?.message,
requestId: tracking.requestId,
duration
});
return new Response(
JSON.stringify({
success: false,
error: error.message,
requestId: tracking.requestId
}),
{
headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
},
status: 500,
}
);
}
});
async function handleNotificationSent(supabase: any, event: any) {
const { transactionId, channel } = event.data;
await supabase
.from('notification_logs')
.update({ status: 'sent' })
.eq('novu_transaction_id', transactionId);
}
async function handleNotificationDelivered(supabase: any, event: any) {
const { transactionId } = event.data;
await supabase
.from('notification_logs')
.update({
status: 'delivered',
delivered_at: new Date().toISOString(),
})
.eq('novu_transaction_id', transactionId);
}
async function handleNotificationRead(supabase: any, event: any) {
const { transactionId } = event.data;
await supabase
.from('notification_logs')
.update({
read_at: new Date().toISOString(),
})
.eq('novu_transaction_id', transactionId);
}
async function handleNotificationFailed(supabase: any, event: any) {
const { transactionId, error } = event.data;
await supabase
.from('notification_logs')
.update({
status: 'failed',
error_message: error,
})
.eq('novu_transaction_id', transactionId);
}