Refactor: Continue implementation plan

This commit is contained in:
gpt-engineer-app[bot]
2025-10-21 12:56:53 +00:00
parent 74860c6774
commit 0d247d9fdd
10 changed files with 254 additions and 65 deletions

View File

@@ -21,7 +21,7 @@ import { getErrorMessage } from './errorHandler';
*/ */
export async function invokeWithTracking<T = any>( export async function invokeWithTracking<T = any>(
functionName: string, functionName: string,
payload: Record<string, unknown> = {}, payload: any = {},
userId?: string, userId?: string,
parentRequestId?: string, parentRequestId?: string,
traceId?: string traceId?: string
@@ -71,7 +71,7 @@ export async function invokeWithTracking<T = any>(
export async function invokeBatchWithTracking<T = any>( export async function invokeBatchWithTracking<T = any>(
operations: Array<{ operations: Array<{
functionName: string; functionName: string;
payload: Record<string, unknown>; payload: any;
}>, }>,
userId?: string userId?: string
): Promise< ): Promise<

View File

@@ -1,4 +1,5 @@
import { supabase } from "@/integrations/supabase/client"; import { supabase } from "@/integrations/supabase/client";
import { invokeWithTracking } from "@/lib/edgeFunctionTracking";
import { logger } from "@/lib/logger"; import { logger } from "@/lib/logger";
import { AppError } from "@/lib/errorHandler"; import { AppError } from "@/lib/errorHandler";
import { z } from "zod"; import { z } from "zod";
@@ -53,14 +54,16 @@ class NotificationService {
return { success: false, error: 'Novu not configured' }; return { success: false, error: 'Novu not configured' };
} }
const { data, error } = await supabase.functions.invoke('update-novu-subscriber', { const { data, error, requestId } = await invokeWithTracking(
body: validated, 'update-novu-subscriber',
}); validated
);
if (error) { if (error) {
logger.error('Edge function error updating Novu subscriber', { logger.error('Edge function error updating Novu subscriber', {
action: 'update_novu_subscriber', action: 'update_novu_subscriber',
userId: validated.subscriberId, userId: validated.subscriberId,
requestId,
error: error.message error: error.message
}); });
throw new AppError( throw new AppError(
@@ -72,7 +75,8 @@ class NotificationService {
logger.info('Novu subscriber updated successfully', { logger.info('Novu subscriber updated successfully', {
action: 'update_novu_subscriber', action: 'update_novu_subscriber',
userId: validated.subscriberId userId: validated.subscriberId,
requestId
}); });
return { success: true }; return { success: true };
@@ -107,14 +111,16 @@ class NotificationService {
return { success: false, error: 'Novu not configured' }; return { success: false, error: 'Novu not configured' };
} }
const { data, error } = await supabase.functions.invoke('create-novu-subscriber', { const { data, error, requestId } = await invokeWithTracking(
body: validated, 'create-novu-subscriber',
}); validated
);
if (error) { if (error) {
logger.error('Edge function error creating Novu subscriber', { logger.error('Edge function error creating Novu subscriber', {
action: 'create_novu_subscriber', action: 'create_novu_subscriber',
userId: validated.subscriberId, userId: validated.subscriberId,
requestId,
error: error.message error: error.message
}); });
throw new AppError( throw new AppError(
@@ -151,7 +157,8 @@ class NotificationService {
logger.info('Novu subscriber created successfully', { logger.info('Novu subscriber created successfully', {
action: 'create_novu_subscriber', action: 'create_novu_subscriber',
userId: validated.subscriberId userId: validated.subscriberId,
requestId
}); });
return { success: true }; return { success: true };
@@ -191,17 +198,19 @@ class NotificationService {
// Update Novu preferences if enabled // Update Novu preferences if enabled
if (novuEnabled) { if (novuEnabled) {
const { error: novuError } = await supabase.functions.invoke('update-novu-preferences', { const { error: novuError, requestId } = await invokeWithTracking(
body: { 'update-novu-preferences',
{
userId, userId,
preferences: validated, preferences: validated,
}, }
}); );
if (novuError) { if (novuError) {
logger.error('Failed to update Novu preferences', { logger.error('Failed to update Novu preferences', {
action: 'update_novu_preferences', action: 'update_novu_preferences',
userId, userId,
requestId,
error: novuError.message error: novuError.message
}); });
throw novuError; throw novuError;
@@ -360,15 +369,17 @@ class NotificationService {
return { success: false, error: 'Novu not configured' }; return { success: false, error: 'Novu not configured' };
} }
const { data, error } = await supabase.functions.invoke('trigger-notification', { const { data, error, requestId } = await invokeWithTracking(
body: payload 'trigger-notification',
}); payload
);
if (error) { if (error) {
logger.error('Failed to trigger notification', { logger.error('Failed to trigger notification', {
action: 'trigger_notification', action: 'trigger_notification',
workflowId: payload.workflowId, workflowId: payload.workflowId,
subscriberId: payload.subscriberId, subscriberId: payload.subscriberId,
requestId,
error: error.message error: error.message
}); });
throw error; throw error;
@@ -378,7 +389,8 @@ class NotificationService {
action: 'trigger_notification', action: 'trigger_notification',
workflowId: payload.workflowId, workflowId: payload.workflowId,
subscriberId: payload.subscriberId, subscriberId: payload.subscriberId,
transactionId: data?.transactionId transactionId: data?.transactionId,
requestId
}); });
return { success: true }; return { success: true };
@@ -407,14 +419,16 @@ class NotificationService {
action: string; action: string;
}): Promise<void> { }): Promise<void> {
try { try {
const { error } = await supabase.functions.invoke('notify-moderators-submission', { const { error, requestId } = await invokeWithTracking(
body: payload 'notify-moderators-submission',
}); payload
);
if (error) { if (error) {
logger.error('Failed to notify moderators', { logger.error('Failed to notify moderators', {
action: 'notify_moderators', action: 'notify_moderators',
submissionId: payload.submission_id, submissionId: payload.submission_id,
requestId,
error: error.message error: error.message
}); });
throw error; throw error;
@@ -422,7 +436,8 @@ class NotificationService {
logger.info('Moderators notified successfully', { logger.info('Moderators notified successfully', {
action: 'notify_moderators', action: 'notify_moderators',
submissionId: payload.submission_id submissionId: payload.submission_id,
requestId
}); });
} catch (error) { } catch (error) {
logger.error('Error notifying moderators', { logger.error('Error notifying moderators', {

View File

@@ -1,8 +1,9 @@
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
import { startRequest, endRequest } from "../_shared/logger.ts";
const corsHeaders = { const corsHeaders = {
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, x-request-id',
}; };
interface IPLocationResponse { interface IPLocationResponse {
@@ -181,6 +182,8 @@ serve(async (req) => {
return new Response(null, { headers: corsHeaders }); return new Response(null, { headers: corsHeaders });
} }
const tracking = startRequest('detect-location');
try { try {
// Get the client's IP address // Get the client's IP address
const forwarded = req.headers.get('x-forwarded-for'); const forwarded = req.headers.get('x-forwarded-for');
@@ -252,15 +255,19 @@ serve(async (req) => {
console.log('[Location] Location detected:', { console.log('[Location] Location detected:', {
country: result.country, country: result.country,
countryCode: result.countryCode, countryCode: result.countryCode,
measurementSystem: result.measurementSystem measurementSystem: result.measurementSystem,
requestId: tracking.requestId
}); });
endRequest(tracking, 200);
return new Response( return new Response(
JSON.stringify(result), JSON.stringify({ ...result, requestId: tracking.requestId }),
{ {
headers: { headers: {
...corsHeaders, ...corsHeaders,
'Content-Type': 'application/json' 'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
} }
} }
); );
@@ -273,9 +280,12 @@ serve(async (req) => {
console.error('[Location Detection Error]', { console.error('[Location Detection Error]', {
error: errorMessage, error: errorMessage,
stack: errorStack, stack: errorStack,
hasIP: true // IP removed for PII protection hasIP: true, // IP removed for PII protection
requestId: tracking.requestId
}); });
endRequest(tracking, 500, errorMessage);
// Return default (metric) with 500 status to indicate error occurred // Return default (metric) with 500 status to indicate error occurred
// This allows proper error monitoring while still providing fallback data // This allows proper error monitoring while still providing fallback data
const defaultResult: IPLocationResponse = { const defaultResult: IPLocationResponse = {
@@ -288,12 +298,14 @@ serve(async (req) => {
JSON.stringify({ JSON.stringify({
...defaultResult, ...defaultResult,
error: errorMessage, error: errorMessage,
fallback: true fallback: true,
requestId: tracking.requestId
}), }),
{ {
headers: { headers: {
...corsHeaders, ...corsHeaders,
'Content-Type': 'application/json' 'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
}, },
status: 500 status: 500
} }

View File

@@ -1,10 +1,11 @@
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; 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 { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4";
import { Novu } from "npm:@novu/api@1.6.0"; import { Novu } from "npm:@novu/api@1.6.0";
import { startRequest, endRequest } from "../_shared/logger.ts";
const corsHeaders = { const corsHeaders = {
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, x-request-id',
}; };
const TOPICS = { const TOPICS = {
@@ -17,6 +18,8 @@ serve(async (req) => {
return new Response(null, { headers: corsHeaders }); return new Response(null, { headers: corsHeaders });
} }
const tracking = startRequest('manage-moderator-topic');
try { try {
const novuApiKey = Deno.env.get('NOVU_API_KEY'); const novuApiKey = Deno.env.get('NOVU_API_KEY');
if (!novuApiKey) { if (!novuApiKey) {
@@ -35,7 +38,7 @@ serve(async (req) => {
throw new Error('Action must be either "add" or "remove"'); throw new Error('Action must be either "add" or "remove"');
} }
console.log(`${action === 'add' ? 'Adding' : 'Removing'} user ${userId} ${action === 'add' ? 'to' : 'from'} moderator topics`); console.log(`${action === 'add' ? 'Adding' : 'Removing'} user ${userId} ${action === 'add' ? 'to' : 'from'} moderator topics`, { requestId: tracking.requestId });
const topics = [TOPICS.MODERATION_SUBMISSIONS, TOPICS.MODERATION_REPORTS]; const topics = [TOPICS.MODERATION_SUBMISSIONS, TOPICS.MODERATION_REPORTS];
const results = []; const results = [];
@@ -70,28 +73,42 @@ serve(async (req) => {
const allSuccess = results.every(r => r.success); const allSuccess = results.every(r => r.success);
endRequest(tracking, allSuccess ? 200 : 207);
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
success: allSuccess, success: allSuccess,
userId, userId,
action, action,
results, results,
requestId: tracking.requestId
}), }),
{ {
headers: { ...corsHeaders, 'Content-Type': 'application/json' }, headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
},
status: allSuccess ? 200 : 207, // 207 = Multi-Status (partial success) status: allSuccess ? 200 : 207, // 207 = Multi-Status (partial success)
} }
); );
} catch (error: any) { } catch (error: any) {
console.error('Error managing moderator topic:', error); console.error('Error managing moderator topic:', error);
endRequest(tracking, 500, error.message);
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
success: false, success: false,
error: error.message, error: error.message,
requestId: tracking.requestId
}), }),
{ {
headers: { ...corsHeaders, 'Content-Type': 'application/json' }, headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
},
status: 500, status: 500,
} }
); );

View File

@@ -1,10 +1,11 @@
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; 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 { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4";
import { Novu } from "npm:@novu/api@1.6.0"; import { Novu } from "npm:@novu/api@1.6.0";
import { startRequest, endRequest } from "../_shared/logger.ts";
const corsHeaders = { const corsHeaders = {
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, x-request-id',
}; };
serve(async (req) => { serve(async (req) => {
@@ -12,6 +13,8 @@ serve(async (req) => {
return new Response(null, { headers: corsHeaders }); return new Response(null, { headers: corsHeaders });
} }
const tracking = startRequest('migrate-novu-users');
try { try {
const supabaseUrl = Deno.env.get('SUPABASE_URL')!; const supabaseUrl = Deno.env.get('SUPABASE_URL')!;
const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!; const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!;
@@ -53,14 +56,21 @@ serve(async (req) => {
if (profilesError) throw profilesError; if (profilesError) throw profilesError;
if (!profiles || profiles.length === 0) { if (!profiles || profiles.length === 0) {
endRequest(tracking, 200);
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
success: true, success: true,
message: 'No users to migrate', message: 'No users to migrate',
results: [], results: [],
requestId: tracking.requestId
}), }),
{ {
headers: { ...corsHeaders, 'Content-Type': 'application/json' }, headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
},
status: 200, status: 200,
} }
); );
@@ -127,27 +137,41 @@ serve(async (req) => {
await new Promise(resolve => setTimeout(resolve, 100)); await new Promise(resolve => setTimeout(resolve, 100));
} }
endRequest(tracking, 200);
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
success: true, success: true,
total: profiles.length, total: profiles.length,
results, results,
requestId: tracking.requestId
}), }),
{ {
headers: { ...corsHeaders, 'Content-Type': 'application/json' }, headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
},
status: 200, status: 200,
} }
); );
} catch (error: any) { } catch (error: any) {
console.error('Error migrating Novu users:', error); console.error('Error migrating Novu users:', error);
endRequest(tracking, 500, error.message);
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
success: false, success: false,
error: error.message, error: error.message,
requestId: tracking.requestId
}), }),
{ {
headers: { ...corsHeaders, 'Content-Type': 'application/json' }, headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
},
status: 500, status: 500,
} }
); );

View File

@@ -1,9 +1,10 @@
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; 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 { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4";
import { startRequest, endRequest } from "../_shared/logger.ts";
const corsHeaders = { const corsHeaders = {
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, x-request-id',
}; };
interface NotificationPayload { interface NotificationPayload {
@@ -22,6 +23,8 @@ serve(async (req) => {
return new Response(null, { headers: corsHeaders }); return new Response(null, { headers: corsHeaders });
} }
const tracking = startRequest('notify-moderators-report');
try { try {
const supabaseUrl = Deno.env.get('SUPABASE_URL')!; const supabaseUrl = Deno.env.get('SUPABASE_URL')!;
const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!; const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!;
@@ -34,6 +37,7 @@ serve(async (req) => {
reportId: payload.reportId, reportId: payload.reportId,
reportType: payload.reportType, reportType: payload.reportType,
reportedEntityType: payload.reportedEntityType, reportedEntityType: payload.reportedEntityType,
requestId: tracking.requestId
}); });
// Calculate relative time // Calculate relative time
@@ -127,27 +131,41 @@ serve(async (req) => {
console.log('Notification triggered successfully:', result); console.log('Notification triggered successfully:', result);
endRequest(tracking, 200);
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
success: true, success: true,
transactionId: result?.transactionId, transactionId: result?.transactionId,
payload: notificationPayload, payload: notificationPayload,
requestId: tracking.requestId
}), }),
{ {
headers: { ...corsHeaders, 'Content-Type': 'application/json' }, headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
},
status: 200, status: 200,
} }
); );
} catch (error: any) { } catch (error: any) {
console.error('Error in notify-moderators-report:', error); console.error('Error in notify-moderators-report:', error);
endRequest(tracking, 500, error.message);
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
success: false, success: false,
error: error.message, error: error.message,
requestId: tracking.requestId
}), }),
{ {
headers: { ...corsHeaders, 'Content-Type': 'application/json' }, headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
},
status: 500, status: 500,
} }
); );

View File

@@ -1,9 +1,10 @@
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'; import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'; import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';
import { startRequest, endRequest } from "../_shared/logger.ts";
const corsHeaders = { const corsHeaders = {
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, x-request-id',
}; };
serve(async (req) => { serve(async (req) => {
@@ -11,6 +12,8 @@ serve(async (req) => {
return new Response(null, { headers: corsHeaders }); return new Response(null, { headers: corsHeaders });
} }
const tracking = startRequest('process-scheduled-deletions');
try { try {
// Use service role for admin operations // Use service role for admin operations
const supabaseAdmin = createClient( const supabaseAdmin = createClient(
@@ -18,7 +21,7 @@ serve(async (req) => {
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? '' Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? ''
); );
console.log('Processing scheduled account deletions...'); console.log('Processing scheduled account deletions...', { requestId: tracking.requestId });
// Find confirmed deletion requests that are past their scheduled date // Find confirmed deletion requests that are past their scheduled date
const { data: pendingDeletions, error: fetchError } = await supabaseAdmin const { data: pendingDeletions, error: fetchError } = await supabaseAdmin
@@ -33,15 +36,23 @@ serve(async (req) => {
if (!pendingDeletions || pendingDeletions.length === 0) { if (!pendingDeletions || pendingDeletions.length === 0) {
console.log('No deletions to process'); console.log('No deletions to process');
endRequest(tracking, 200);
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
success: true, success: true,
message: 'No deletions to process', message: 'No deletions to process',
processed: 0, processed: 0,
requestId: tracking.requestId
}), }),
{ {
status: 200, status: 200,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }, headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
},
} }
); );
} }
@@ -170,25 +181,42 @@ serve(async (req) => {
console.log(`Processed ${successCount} deletion(s) successfully, ${errorCount} error(s)`); console.log(`Processed ${successCount} deletion(s) successfully, ${errorCount} error(s)`);
endRequest(tracking, 200);
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
success: true, success: true,
message: `Processed ${successCount} deletion(s)`, message: `Processed ${successCount} deletion(s)`,
processed: successCount, processed: successCount,
errors: errorCount, errors: errorCount,
requestId: tracking.requestId
}), }),
{ {
status: 200, status: 200,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }, headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
},
} }
); );
} catch (error) { } catch (error) {
console.error('Error processing scheduled deletions:', error); console.error('Error processing scheduled deletions:', error);
endRequest(tracking, 500, error.message);
return new Response( return new Response(
JSON.stringify({ error: error.message }), JSON.stringify({
error: error.message,
requestId: tracking.requestId
}),
{ {
status: 500, status: 500,
headers: { ...corsHeaders, 'Content-Type': 'application/json' }, headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
},
} }
); );
} }

View File

@@ -1,9 +1,10 @@
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
import { Novu } from "npm:@novu/api@1.6.0"; import { Novu } from "npm:@novu/api@1.6.0";
import { startRequest, endRequest } from "../_shared/logger.ts";
const corsHeaders = { const corsHeaders = {
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, x-request-id',
}; };
serve(async (req) => { serve(async (req) => {
@@ -11,6 +12,8 @@ serve(async (req) => {
return new Response(null, { headers: corsHeaders }); return new Response(null, { headers: corsHeaders });
} }
const tracking = startRequest('trigger-notification');
try { try {
const novuApiKey = Deno.env.get('NOVU_API_KEY'); const novuApiKey = Deno.env.get('NOVU_API_KEY');
@@ -39,7 +42,7 @@ serve(async (req) => {
? { subscriberId } ? { subscriberId }
: { topicKey: topicKey! }; : { topicKey: topicKey! };
console.log('Triggering notification:', { workflowId, recipient }); console.log('Triggering notification:', { workflowId, recipient, requestId: tracking.requestId });
const result = await novu.trigger({ const result = await novu.trigger({
to: recipient, to: recipient,
@@ -50,13 +53,20 @@ serve(async (req) => {
console.log('Notification triggered successfully:', result.data); console.log('Notification triggered successfully:', result.data);
endRequest(tracking, 200);
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
success: true, success: true,
transactionId: result.data.transactionId, transactionId: result.data.transactionId,
requestId: tracking.requestId
}), }),
{ {
headers: { ...corsHeaders, 'Content-Type': 'application/json' }, headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
},
status: 200, status: 200,
} }
); );
@@ -64,13 +74,20 @@ serve(async (req) => {
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
console.error('Error triggering notification:', errorMessage); console.error('Error triggering notification:', errorMessage);
endRequest(tracking, 500, errorMessage);
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
success: false, success: false,
error: errorMessage, error: errorMessage,
requestId: tracking.requestId
}), }),
{ {
headers: { ...corsHeaders, 'Content-Type': 'application/json' }, headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
},
status: 500, status: 500,
} }
); );

View File

@@ -1,9 +1,10 @@
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
import { Novu } from "npm:@novu/api@1.6.0"; import { Novu } from "npm:@novu/api@1.6.0";
import { startRequest, endRequest } from "../_shared/logger.ts";
const corsHeaders = { const corsHeaders = {
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, x-request-id',
}; };
serve(async (req) => { serve(async (req) => {
@@ -11,6 +12,8 @@ serve(async (req) => {
return new Response(null, { headers: corsHeaders }); return new Response(null, { headers: corsHeaders });
} }
const tracking = startRequest('update-novu-subscriber');
try { try {
const novuApiKey = Deno.env.get('NOVU_API_KEY'); const novuApiKey = Deno.env.get('NOVU_API_KEY');
@@ -32,7 +35,7 @@ serve(async (req) => {
data?: Record<string, unknown>; data?: Record<string, unknown>;
}; };
console.log('Updating Novu subscriber:', { subscriberId, email, firstName }); console.log('Updating Novu subscriber:', { subscriberId, email, firstName, requestId: tracking.requestId });
const subscriber = await novu.subscribers.update(subscriberId, { const subscriber = await novu.subscribers.update(subscriberId, {
email, email,
@@ -45,13 +48,20 @@ serve(async (req) => {
console.log('Subscriber updated successfully:', subscriber.data); console.log('Subscriber updated successfully:', subscriber.data);
endRequest(tracking, 200);
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
success: true, success: true,
subscriberId: subscriber.data._id, subscriberId: subscriber.data._id,
requestId: tracking.requestId
}), }),
{ {
headers: { ...corsHeaders, 'Content-Type': 'application/json' }, headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
},
status: 200, status: 200,
} }
); );
@@ -59,13 +69,20 @@ serve(async (req) => {
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
console.error('Error updating Novu subscriber:', errorMessage); console.error('Error updating Novu subscriber:', errorMessage);
endRequest(tracking, 500, errorMessage);
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
success: false, success: false,
error: errorMessage, error: errorMessage,
requestId: tracking.requestId
}), }),
{ {
headers: { ...corsHeaders, 'Content-Type': 'application/json' }, headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
},
status: 500, status: 500,
} }
); );

View File

@@ -1,8 +1,9 @@
import { serve } from "https://deno.land/std@0.190.0/http/server.ts"; import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
import { startRequest, endRequest } from "../_shared/logger.ts";
const corsHeaders = { const corsHeaders = {
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, x-request-id',
}; };
// Comprehensive list of disposable email domains // Comprehensive list of disposable email domains
@@ -74,18 +75,27 @@ const handler = async (req: Request): Promise<Response> => {
return new Response(null, { headers: corsHeaders }); return new Response(null, { headers: corsHeaders });
} }
const tracking = startRequest('validate-email');
try { try {
const { email }: ValidateEmailRequest = await req.json(); const { email }: ValidateEmailRequest = await req.json();
if (!email || typeof email !== 'string') { if (!email || typeof email !== 'string') {
endRequest(tracking, 400, 'Email address is required');
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
valid: false, valid: false,
reason: 'Email address is required' reason: 'Email address is required',
requestId: tracking.requestId
} as ValidationResult), } as ValidationResult),
{ {
status: 400, status: 400,
headers: { ...corsHeaders, 'Content-Type': 'application/json' } headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
}
} }
); );
} }
@@ -93,14 +103,21 @@ const handler = async (req: Request): Promise<Response> => {
// Basic email format validation // Basic email format validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) { if (!emailRegex.test(email)) {
endRequest(tracking, 400, 'Invalid email format');
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
valid: false, valid: false,
reason: 'Invalid email format' reason: 'Invalid email format',
requestId: tracking.requestId
} as ValidationResult), } as ValidationResult),
{ {
status: 400, status: 400,
headers: { ...corsHeaders, 'Content-Type': 'application/json' } headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
}
} }
); );
} }
@@ -111,6 +128,9 @@ const handler = async (req: Request): Promise<Response> => {
// Check if domain is disposable // Check if domain is disposable
if (DISPOSABLE_DOMAINS.has(domain)) { if (DISPOSABLE_DOMAINS.has(domain)) {
console.log(`Blocked disposable email domain: ${domain}`); console.log(`Blocked disposable email domain: ${domain}`);
endRequest(tracking, 400, 'Disposable email domain blocked');
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
valid: false, valid: false,
@@ -119,37 +139,58 @@ const handler = async (req: Request): Promise<Response> => {
'Use a personal email (Gmail, Outlook, Yahoo, etc.)', 'Use a personal email (Gmail, Outlook, Yahoo, etc.)',
'Use your work or school email address', 'Use your work or school email address',
'Use an email from your own domain' 'Use an email from your own domain'
] ],
requestId: tracking.requestId
} as ValidationResult), } as ValidationResult),
{ {
status: 400, status: 400,
headers: { ...corsHeaders, 'Content-Type': 'application/json' } headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
}
} }
); );
} }
// Email is valid // Email is valid
console.log(`Email validated successfully: ${email}`); console.log(`Email validated successfully: ${email}`);
endRequest(tracking, 200);
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
valid: true valid: true,
requestId: tracking.requestId
} as ValidationResult), } as ValidationResult),
{ {
status: 200, status: 200,
headers: { ...corsHeaders, 'Content-Type': 'application/json' } headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
}
} }
); );
} catch (error: any) { } catch (error: any) {
console.error('Error in validate-email function:', error); console.error('Error in validate-email function:', error);
endRequest(tracking, 500, error.message);
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
valid: false, valid: false,
reason: 'Internal server error during email validation' reason: 'Internal server error during email validation',
requestId: tracking.requestId
} as ValidationResult), } as ValidationResult),
{ {
status: 500, status: 500,
headers: { ...corsHeaders, 'Content-Type': 'application/json' } headers: {
...corsHeaders,
'Content-Type': 'application/json',
'X-Request-ID': tracking.requestId
}
} }
); );
} }