mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 12:11:17 -05:00
Refactor: Continue implementation plan
This commit is contained in:
@@ -21,7 +21,7 @@ import { getErrorMessage } from './errorHandler';
|
||||
*/
|
||||
export async function invokeWithTracking<T = any>(
|
||||
functionName: string,
|
||||
payload: Record<string, unknown> = {},
|
||||
payload: any = {},
|
||||
userId?: string,
|
||||
parentRequestId?: string,
|
||||
traceId?: string
|
||||
@@ -71,7 +71,7 @@ export async function invokeWithTracking<T = any>(
|
||||
export async function invokeBatchWithTracking<T = any>(
|
||||
operations: Array<{
|
||||
functionName: string;
|
||||
payload: Record<string, unknown>;
|
||||
payload: any;
|
||||
}>,
|
||||
userId?: string
|
||||
): Promise<
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { supabase } from "@/integrations/supabase/client";
|
||||
import { invokeWithTracking } from "@/lib/edgeFunctionTracking";
|
||||
import { logger } from "@/lib/logger";
|
||||
import { AppError } from "@/lib/errorHandler";
|
||||
import { z } from "zod";
|
||||
@@ -53,14 +54,16 @@ class NotificationService {
|
||||
return { success: false, error: 'Novu not configured' };
|
||||
}
|
||||
|
||||
const { data, error } = await supabase.functions.invoke('update-novu-subscriber', {
|
||||
body: validated,
|
||||
});
|
||||
const { data, error, requestId } = await invokeWithTracking(
|
||||
'update-novu-subscriber',
|
||||
validated
|
||||
);
|
||||
|
||||
if (error) {
|
||||
logger.error('Edge function error updating Novu subscriber', {
|
||||
action: 'update_novu_subscriber',
|
||||
userId: validated.subscriberId,
|
||||
requestId,
|
||||
error: error.message
|
||||
});
|
||||
throw new AppError(
|
||||
@@ -72,7 +75,8 @@ class NotificationService {
|
||||
|
||||
logger.info('Novu subscriber updated successfully', {
|
||||
action: 'update_novu_subscriber',
|
||||
userId: validated.subscriberId
|
||||
userId: validated.subscriberId,
|
||||
requestId
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
@@ -107,14 +111,16 @@ class NotificationService {
|
||||
return { success: false, error: 'Novu not configured' };
|
||||
}
|
||||
|
||||
const { data, error } = await supabase.functions.invoke('create-novu-subscriber', {
|
||||
body: validated,
|
||||
});
|
||||
const { data, error, requestId } = await invokeWithTracking(
|
||||
'create-novu-subscriber',
|
||||
validated
|
||||
);
|
||||
|
||||
if (error) {
|
||||
logger.error('Edge function error creating Novu subscriber', {
|
||||
action: 'create_novu_subscriber',
|
||||
userId: validated.subscriberId,
|
||||
requestId,
|
||||
error: error.message
|
||||
});
|
||||
throw new AppError(
|
||||
@@ -151,7 +157,8 @@ class NotificationService {
|
||||
|
||||
logger.info('Novu subscriber created successfully', {
|
||||
action: 'create_novu_subscriber',
|
||||
userId: validated.subscriberId
|
||||
userId: validated.subscriberId,
|
||||
requestId
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
@@ -191,17 +198,19 @@ class NotificationService {
|
||||
|
||||
// Update Novu preferences if enabled
|
||||
if (novuEnabled) {
|
||||
const { error: novuError } = await supabase.functions.invoke('update-novu-preferences', {
|
||||
body: {
|
||||
const { error: novuError, requestId } = await invokeWithTracking(
|
||||
'update-novu-preferences',
|
||||
{
|
||||
userId,
|
||||
preferences: validated,
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
if (novuError) {
|
||||
logger.error('Failed to update Novu preferences', {
|
||||
action: 'update_novu_preferences',
|
||||
userId,
|
||||
requestId,
|
||||
error: novuError.message
|
||||
});
|
||||
throw novuError;
|
||||
@@ -360,15 +369,17 @@ class NotificationService {
|
||||
return { success: false, error: 'Novu not configured' };
|
||||
}
|
||||
|
||||
const { data, error } = await supabase.functions.invoke('trigger-notification', {
|
||||
body: payload
|
||||
});
|
||||
const { data, error, requestId } = await invokeWithTracking(
|
||||
'trigger-notification',
|
||||
payload
|
||||
);
|
||||
|
||||
if (error) {
|
||||
logger.error('Failed to trigger notification', {
|
||||
action: 'trigger_notification',
|
||||
workflowId: payload.workflowId,
|
||||
subscriberId: payload.subscriberId,
|
||||
requestId,
|
||||
error: error.message
|
||||
});
|
||||
throw error;
|
||||
@@ -378,7 +389,8 @@ class NotificationService {
|
||||
action: 'trigger_notification',
|
||||
workflowId: payload.workflowId,
|
||||
subscriberId: payload.subscriberId,
|
||||
transactionId: data?.transactionId
|
||||
transactionId: data?.transactionId,
|
||||
requestId
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
@@ -407,14 +419,16 @@ class NotificationService {
|
||||
action: string;
|
||||
}): Promise<void> {
|
||||
try {
|
||||
const { error } = await supabase.functions.invoke('notify-moderators-submission', {
|
||||
body: payload
|
||||
});
|
||||
const { error, requestId } = await invokeWithTracking(
|
||||
'notify-moderators-submission',
|
||||
payload
|
||||
);
|
||||
|
||||
if (error) {
|
||||
logger.error('Failed to notify moderators', {
|
||||
action: 'notify_moderators',
|
||||
submissionId: payload.submission_id,
|
||||
requestId,
|
||||
error: error.message
|
||||
});
|
||||
throw error;
|
||||
@@ -422,7 +436,8 @@ class NotificationService {
|
||||
|
||||
logger.info('Moderators notified successfully', {
|
||||
action: 'notify_moderators',
|
||||
submissionId: payload.submission_id
|
||||
submissionId: payload.submission_id,
|
||||
requestId
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Error notifying moderators', {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
|
||||
import { startRequest, endRequest } from "../_shared/logger.ts";
|
||||
|
||||
const corsHeaders = {
|
||||
'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 {
|
||||
@@ -181,6 +182,8 @@ serve(async (req) => {
|
||||
return new Response(null, { headers: corsHeaders });
|
||||
}
|
||||
|
||||
const tracking = startRequest('detect-location');
|
||||
|
||||
try {
|
||||
// Get the client's IP address
|
||||
const forwarded = req.headers.get('x-forwarded-for');
|
||||
@@ -252,15 +255,19 @@ serve(async (req) => {
|
||||
console.log('[Location] Location detected:', {
|
||||
country: result.country,
|
||||
countryCode: result.countryCode,
|
||||
measurementSystem: result.measurementSystem
|
||||
measurementSystem: result.measurementSystem,
|
||||
requestId: tracking.requestId
|
||||
});
|
||||
|
||||
endRequest(tracking, 200);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify(result),
|
||||
JSON.stringify({ ...result, requestId: tracking.requestId }),
|
||||
{
|
||||
headers: {
|
||||
...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]', {
|
||||
error: errorMessage,
|
||||
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
|
||||
// This allows proper error monitoring while still providing fallback data
|
||||
const defaultResult: IPLocationResponse = {
|
||||
@@ -288,12 +298,14 @@ serve(async (req) => {
|
||||
JSON.stringify({
|
||||
...defaultResult,
|
||||
error: errorMessage,
|
||||
fallback: true
|
||||
fallback: true,
|
||||
requestId: tracking.requestId
|
||||
}),
|
||||
{
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json'
|
||||
'Content-Type': 'application/json',
|
||||
'X-Request-ID': tracking.requestId
|
||||
},
|
||||
status: 500
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
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 { 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',
|
||||
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, x-request-id',
|
||||
};
|
||||
|
||||
const TOPICS = {
|
||||
@@ -17,6 +18,8 @@ serve(async (req) => {
|
||||
return new Response(null, { headers: corsHeaders });
|
||||
}
|
||||
|
||||
const tracking = startRequest('manage-moderator-topic');
|
||||
|
||||
try {
|
||||
const novuApiKey = Deno.env.get('NOVU_API_KEY');
|
||||
if (!novuApiKey) {
|
||||
@@ -35,7 +38,7 @@ serve(async (req) => {
|
||||
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 results = [];
|
||||
@@ -70,28 +73,42 @@ serve(async (req) => {
|
||||
|
||||
const allSuccess = results.every(r => r.success);
|
||||
|
||||
endRequest(tracking, allSuccess ? 200 : 207);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: allSuccess,
|
||||
userId,
|
||||
action,
|
||||
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)
|
||||
}
|
||||
);
|
||||
} catch (error: any) {
|
||||
console.error('Error managing moderator topic:', error);
|
||||
|
||||
endRequest(tracking, 500, error.message);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
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,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
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 { 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',
|
||||
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, x-request-id',
|
||||
};
|
||||
|
||||
serve(async (req) => {
|
||||
@@ -12,6 +13,8 @@ serve(async (req) => {
|
||||
return new Response(null, { headers: corsHeaders });
|
||||
}
|
||||
|
||||
const tracking = startRequest('migrate-novu-users');
|
||||
|
||||
try {
|
||||
const supabaseUrl = Deno.env.get('SUPABASE_URL')!;
|
||||
const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!;
|
||||
@@ -53,14 +56,21 @@ serve(async (req) => {
|
||||
if (profilesError) throw profilesError;
|
||||
|
||||
if (!profiles || profiles.length === 0) {
|
||||
endRequest(tracking, 200);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
message: 'No users to migrate',
|
||||
results: [],
|
||||
requestId: tracking.requestId
|
||||
}),
|
||||
{
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json',
|
||||
'X-Request-ID': tracking.requestId
|
||||
},
|
||||
status: 200,
|
||||
}
|
||||
);
|
||||
@@ -127,27 +137,41 @@ serve(async (req) => {
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
}
|
||||
|
||||
endRequest(tracking, 200);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
total: profiles.length,
|
||||
results,
|
||||
requestId: tracking.requestId
|
||||
}),
|
||||
{
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json',
|
||||
'X-Request-ID': tracking.requestId
|
||||
},
|
||||
status: 200,
|
||||
}
|
||||
);
|
||||
} catch (error: any) {
|
||||
console.error('Error migrating Novu users:', error);
|
||||
|
||||
endRequest(tracking, 500, error.message);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
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,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
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 { startRequest, endRequest } from "../_shared/logger.ts";
|
||||
|
||||
const corsHeaders = {
|
||||
'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 {
|
||||
@@ -22,6 +23,8 @@ serve(async (req) => {
|
||||
return new Response(null, { headers: corsHeaders });
|
||||
}
|
||||
|
||||
const tracking = startRequest('notify-moderators-report');
|
||||
|
||||
try {
|
||||
const supabaseUrl = Deno.env.get('SUPABASE_URL')!;
|
||||
const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!;
|
||||
@@ -34,6 +37,7 @@ serve(async (req) => {
|
||||
reportId: payload.reportId,
|
||||
reportType: payload.reportType,
|
||||
reportedEntityType: payload.reportedEntityType,
|
||||
requestId: tracking.requestId
|
||||
});
|
||||
|
||||
// Calculate relative time
|
||||
@@ -127,27 +131,41 @@ serve(async (req) => {
|
||||
|
||||
console.log('Notification triggered successfully:', result);
|
||||
|
||||
endRequest(tracking, 200);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
transactionId: result?.transactionId,
|
||||
payload: notificationPayload,
|
||||
requestId: tracking.requestId
|
||||
}),
|
||||
{
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json',
|
||||
'X-Request-ID': tracking.requestId
|
||||
},
|
||||
status: 200,
|
||||
}
|
||||
);
|
||||
} catch (error: any) {
|
||||
console.error('Error in notify-moderators-report:', error);
|
||||
|
||||
endRequest(tracking, 500, error.message);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
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,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
|
||||
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';
|
||||
import { startRequest, endRequest } from "../_shared/logger.ts";
|
||||
|
||||
const corsHeaders = {
|
||||
'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) => {
|
||||
@@ -11,6 +12,8 @@ serve(async (req) => {
|
||||
return new Response(null, { headers: corsHeaders });
|
||||
}
|
||||
|
||||
const tracking = startRequest('process-scheduled-deletions');
|
||||
|
||||
try {
|
||||
// Use service role for admin operations
|
||||
const supabaseAdmin = createClient(
|
||||
@@ -18,7 +21,7 @@ serve(async (req) => {
|
||||
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
|
||||
const { data: pendingDeletions, error: fetchError } = await supabaseAdmin
|
||||
@@ -33,15 +36,23 @@ serve(async (req) => {
|
||||
|
||||
if (!pendingDeletions || pendingDeletions.length === 0) {
|
||||
console.log('No deletions to process');
|
||||
|
||||
endRequest(tracking, 200);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
message: 'No deletions to process',
|
||||
processed: 0,
|
||||
requestId: tracking.requestId
|
||||
}),
|
||||
{
|
||||
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)`);
|
||||
|
||||
endRequest(tracking, 200);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
message: `Processed ${successCount} deletion(s)`,
|
||||
processed: successCount,
|
||||
errors: errorCount,
|
||||
requestId: tracking.requestId
|
||||
}),
|
||||
{
|
||||
status: 200,
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json',
|
||||
'X-Request-ID': tracking.requestId
|
||||
},
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Error processing scheduled deletions:', error);
|
||||
|
||||
endRequest(tracking, 500, error.message);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({ error: error.message }),
|
||||
JSON.stringify({
|
||||
error: error.message,
|
||||
requestId: tracking.requestId
|
||||
}),
|
||||
{
|
||||
status: 500,
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json',
|
||||
'X-Request-ID': tracking.requestId
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { serve } from "https://deno.land/std@0.168.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',
|
||||
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, x-request-id',
|
||||
};
|
||||
|
||||
serve(async (req) => {
|
||||
@@ -11,6 +12,8 @@ serve(async (req) => {
|
||||
return new Response(null, { headers: corsHeaders });
|
||||
}
|
||||
|
||||
const tracking = startRequest('trigger-notification');
|
||||
|
||||
try {
|
||||
const novuApiKey = Deno.env.get('NOVU_API_KEY');
|
||||
|
||||
@@ -39,7 +42,7 @@ serve(async (req) => {
|
||||
? { subscriberId }
|
||||
: { topicKey: topicKey! };
|
||||
|
||||
console.log('Triggering notification:', { workflowId, recipient });
|
||||
console.log('Triggering notification:', { workflowId, recipient, requestId: tracking.requestId });
|
||||
|
||||
const result = await novu.trigger({
|
||||
to: recipient,
|
||||
@@ -50,13 +53,20 @@ serve(async (req) => {
|
||||
|
||||
console.log('Notification triggered successfully:', result.data);
|
||||
|
||||
endRequest(tracking, 200);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
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,
|
||||
}
|
||||
);
|
||||
@@ -64,13 +74,20 @@ serve(async (req) => {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
||||
console.error('Error triggering notification:', errorMessage);
|
||||
|
||||
endRequest(tracking, 500, errorMessage);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
error: errorMessage,
|
||||
requestId: tracking.requestId
|
||||
}),
|
||||
{
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json',
|
||||
'X-Request-ID': tracking.requestId
|
||||
},
|
||||
status: 500,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { serve } from "https://deno.land/std@0.168.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',
|
||||
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, x-request-id',
|
||||
};
|
||||
|
||||
serve(async (req) => {
|
||||
@@ -11,6 +12,8 @@ serve(async (req) => {
|
||||
return new Response(null, { headers: corsHeaders });
|
||||
}
|
||||
|
||||
const tracking = startRequest('update-novu-subscriber');
|
||||
|
||||
try {
|
||||
const novuApiKey = Deno.env.get('NOVU_API_KEY');
|
||||
|
||||
@@ -32,7 +35,7 @@ serve(async (req) => {
|
||||
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, {
|
||||
email,
|
||||
@@ -45,13 +48,20 @@ serve(async (req) => {
|
||||
|
||||
console.log('Subscriber updated successfully:', subscriber.data);
|
||||
|
||||
endRequest(tracking, 200);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
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,
|
||||
}
|
||||
);
|
||||
@@ -59,13 +69,20 @@ serve(async (req) => {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
||||
console.error('Error updating Novu subscriber:', errorMessage);
|
||||
|
||||
endRequest(tracking, 500, errorMessage);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
error: errorMessage,
|
||||
requestId: tracking.requestId
|
||||
}),
|
||||
{
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json',
|
||||
'X-Request-ID': tracking.requestId
|
||||
},
|
||||
status: 500,
|
||||
}
|
||||
);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
|
||||
import { startRequest, endRequest } from "../_shared/logger.ts";
|
||||
|
||||
const corsHeaders = {
|
||||
'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
|
||||
@@ -74,18 +75,27 @@ const handler = async (req: Request): Promise<Response> => {
|
||||
return new Response(null, { headers: corsHeaders });
|
||||
}
|
||||
|
||||
const tracking = startRequest('validate-email');
|
||||
|
||||
try {
|
||||
const { email }: ValidateEmailRequest = await req.json();
|
||||
|
||||
if (!email || typeof email !== 'string') {
|
||||
endRequest(tracking, 400, 'Email address is required');
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
valid: false,
|
||||
reason: 'Email address is required'
|
||||
reason: 'Email address is required',
|
||||
requestId: tracking.requestId
|
||||
} as ValidationResult),
|
||||
{
|
||||
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
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(email)) {
|
||||
endRequest(tracking, 400, 'Invalid email format');
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
valid: false,
|
||||
reason: 'Invalid email format'
|
||||
reason: 'Invalid email format',
|
||||
requestId: tracking.requestId
|
||||
} as ValidationResult),
|
||||
{
|
||||
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
|
||||
if (DISPOSABLE_DOMAINS.has(domain)) {
|
||||
console.log(`Blocked disposable email domain: ${domain}`);
|
||||
|
||||
endRequest(tracking, 400, 'Disposable email domain blocked');
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
valid: false,
|
||||
@@ -119,37 +139,58 @@ const handler = async (req: Request): Promise<Response> => {
|
||||
'Use a personal email (Gmail, Outlook, Yahoo, etc.)',
|
||||
'Use your work or school email address',
|
||||
'Use an email from your own domain'
|
||||
]
|
||||
],
|
||||
requestId: tracking.requestId
|
||||
} as ValidationResult),
|
||||
{
|
||||
status: 400,
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json',
|
||||
'X-Request-ID': tracking.requestId
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Email is valid
|
||||
console.log(`Email validated successfully: ${email}`);
|
||||
|
||||
endRequest(tracking, 200);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
valid: true
|
||||
valid: true,
|
||||
requestId: tracking.requestId
|
||||
} as ValidationResult),
|
||||
{
|
||||
status: 200,
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json',
|
||||
'X-Request-ID': tracking.requestId
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('Error in validate-email function:', error);
|
||||
|
||||
endRequest(tracking, 500, error.message);
|
||||
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
valid: false,
|
||||
reason: 'Internal server error during email validation'
|
||||
reason: 'Internal server error during email validation',
|
||||
requestId: tracking.requestId
|
||||
} as ValidationResult),
|
||||
{
|
||||
status: 500,
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' }
|
||||
headers: {
|
||||
...corsHeaders,
|
||||
'Content-Type': 'application/json',
|
||||
'X-Request-ID': tracking.requestId
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user