Files
thrilltrack-explorer/supabase/functions/novu-webhook/index.ts
gpt-engineer-app[bot] fa57d497af Add database persistence to 8 edge functions
Implement Phase 1 by adding error span logging and database persistence to 8 edge functions that already log errors:
- detect-location
- export-user-data
- notify-moderators-submission
- novu-webhook
- send-escalation-notification
- send-password-added-email
- resend-deletion-code
- merge-contact-tickets

This update introduces startSpan/endSpan and logSpanToDatabase usage in catch blocks to ensure errors are recorded in the monitoring database.
2025-11-11 18:30:31 +00:00

140 lines
3.9 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, logSpanToDatabase, startSpan, endSpan } 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
});
// Persist error to database for monitoring
const errorSpan = startSpan('novu-webhook-error', 'SERVER');
endSpan(errorSpan, 'error', error);
logSpanToDatabase(errorSpan, tracking.requestId);
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);
}