Migrate Phase 3 Webhook and Utilities

Extend createEdgeFunction usage to novu-webhook, seed-test-data, and sitemap by removing manual boilerplate (CORS, auth, tracking, error handling) and replacing logging with span-based tracing; wire in EdgeFunctionContext for supabase, user, span, and requestId; preserve core logic including webhook validation, data seeding utilities, and sitemap caching.
This commit is contained in:
gpt-engineer-app[bot]
2025-11-11 21:17:32 +00:00
parent 96b7594738
commit 9ee84b31ff
3 changed files with 183 additions and 256 deletions

View File

@@ -1,111 +1,72 @@
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 { createEdgeFunction, type EdgeFunctionContext } from '../_shared/edgeFunctionWrapper.ts';
import { corsHeaders } from '../_shared/cors.ts';
import { edgeLogger, logSpanToDatabase, startSpan, endSpan } from '../_shared/logger.ts';
import { addSpanEvent } 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(createEdgeFunction({
name: 'novu-webhook',
requireAuth: false, // Webhooks don't use standard auth
useServiceRole: true, // Need service role to update notification_logs
corsHeaders,
}, async (req, { span, supabase, requestId }: EdgeFunctionContext) => {
const event = await req.json();
serve(async (req) => {
const tracking = startRequest();
if (req.method === 'OPTIONS') {
return new Response(null, { headers: corsHeaders });
addSpanEvent(span, 'received_webhook_event', {
eventType: event.type
});
// Handle different webhook events
switch (event.type) {
case 'notification.sent':
await handleNotificationSent(supabase, event, span);
break;
case 'notification.delivered':
await handleNotificationDelivered(supabase, event, span);
break;
case 'notification.read':
await handleNotificationRead(supabase, event, span);
break;
case 'notification.failed':
await handleNotificationFailed(supabase, event, span);
break;
default:
addSpanEvent(span, 'unhandled_event_type', {
eventType: event.type
});
}
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
});
return new Response(
JSON.stringify({ success: true, requestId }),
{
headers: {
'Content-Type': 'application/json'
},
status: 200,
}
);
}));
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) {
async function handleNotificationSent(supabase: any, event: any, span: any) {
const { transactionId, channel } = event.data;
addSpanEvent(span, 'notification_sent_update', {
transactionId,
channel
});
await supabase
.from('notification_logs')
.update({ status: 'sent' })
.eq('novu_transaction_id', transactionId);
}
async function handleNotificationDelivered(supabase: any, event: any) {
async function handleNotificationDelivered(supabase: any, event: any, span: any) {
const { transactionId } = event.data;
addSpanEvent(span, 'notification_delivered_update', {
transactionId
});
await supabase
.from('notification_logs')
.update({
@@ -115,9 +76,13 @@ async function handleNotificationDelivered(supabase: any, event: any) {
.eq('novu_transaction_id', transactionId);
}
async function handleNotificationRead(supabase: any, event: any) {
async function handleNotificationRead(supabase: any, event: any, span: any) {
const { transactionId } = event.data;
addSpanEvent(span, 'notification_read_update', {
transactionId
});
await supabase
.from('notification_logs')
.update({
@@ -126,9 +91,14 @@ async function handleNotificationRead(supabase: any, event: any) {
.eq('novu_transaction_id', transactionId);
}
async function handleNotificationFailed(supabase: any, event: any) {
async function handleNotificationFailed(supabase: any, event: any, span: any) {
const { transactionId, error } = event.data;
addSpanEvent(span, 'notification_failed_update', {
transactionId,
error
});
await supabase
.from('notification_logs')
.update({