mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 08:11:13 -05:00
Fix remaining compliance violations
This commit is contained in:
@@ -273,20 +273,24 @@ export function ContentTabs() {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5 xl:grid-cols-6 2xl:grid-cols-7 3xl:grid-cols-8 gap-4 lg:gap-5 xl:gap-4 2xl:gap-5">
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5 xl:grid-cols-6 2xl:grid-cols-7 3xl:grid-cols-8 gap-4 lg:gap-5 xl:gap-4 2xl:gap-5">
|
||||||
{recentlyOpened.map((entity: any) => (
|
{recentlyOpened.map((entity) => (
|
||||||
entity.entityType === 'park' ? (
|
entity.entityType === 'park' ? (
|
||||||
<div key={entity.id} className="relative">
|
<div key={entity.id} className="relative">
|
||||||
<ParkCard park={entity} />
|
<ParkCard park={entity} />
|
||||||
<Badge className="absolute top-2 right-2 bg-green-500/90 text-white backdrop-blur-sm">
|
{entity.opening_date && (
|
||||||
{new Date(entity.opening_date).getFullYear()}
|
<Badge className="absolute top-2 right-2 bg-green-500/90 text-white backdrop-blur-sm">
|
||||||
</Badge>
|
{new Date(entity.opening_date).getFullYear()}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div key={entity.id} className="relative">
|
<div key={entity.id} className="relative">
|
||||||
<RideCard ride={entity} />
|
<RideCard ride={entity} />
|
||||||
<Badge className="absolute top-2 right-2 bg-green-500/90 text-white backdrop-blur-sm">
|
{entity.opening_date && (
|
||||||
{new Date(entity.opening_date).getFullYear()}
|
<Badge className="absolute top-2 right-2 bg-green-500/90 text-white backdrop-blur-sm">
|
||||||
</Badge>
|
{new Date(entity.opening_date).getFullYear()}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -19,10 +19,11 @@ export function NotificationCenter() {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleNotificationClick = (notification: any) => {
|
const handleNotificationClick = (notification: { data?: Record<string, unknown> }) => {
|
||||||
// Handle navigation based on notification payload
|
// Handle navigation based on notification payload
|
||||||
if (notification.data?.url) {
|
const data = notification.data as { url?: string } | undefined;
|
||||||
navigate(notification.data.url);
|
if (data?.url) {
|
||||||
|
navigate(data.url);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ import { useAuth } from './useAuth';
|
|||||||
import { useUserRole } from './useUserRole';
|
import { useUserRole } from './useUserRole';
|
||||||
import { useToast } from './use-toast';
|
import { useToast } from './use-toast';
|
||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
|
import type { Json } from '@/integrations/supabase/types';
|
||||||
|
|
||||||
interface AdminSetting {
|
interface AdminSetting {
|
||||||
id: string;
|
id: string;
|
||||||
setting_key: string;
|
setting_key: string;
|
||||||
setting_value: any;
|
setting_value: unknown;
|
||||||
category: string;
|
category: string;
|
||||||
description: string;
|
description: string;
|
||||||
}
|
}
|
||||||
@@ -46,11 +47,11 @@ export function useAdminSettings() {
|
|||||||
}, [settings]);
|
}, [settings]);
|
||||||
|
|
||||||
const updateSettingMutation = useMutation({
|
const updateSettingMutation = useMutation({
|
||||||
mutationFn: async ({ key, value }: { key: string; value: any }) => {
|
mutationFn: async ({ key, value }: { key: string; value: unknown }) => {
|
||||||
const { error } = await supabase
|
const { error } = await supabase
|
||||||
.from('admin_settings')
|
.from('admin_settings')
|
||||||
.update({
|
.update({
|
||||||
setting_value: value,
|
setting_value: value as Json,
|
||||||
updated_by: user?.id,
|
updated_by: user?.id,
|
||||||
updated_at: new Date().toISOString()
|
updated_at: new Date().toISOString()
|
||||||
})
|
})
|
||||||
@@ -65,7 +66,7 @@ export function useAdminSettings() {
|
|||||||
description: "The setting has been saved successfully.",
|
description: "The setting has been saved successfully.",
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onError: (error: any) => {
|
onError: (error: Error) => {
|
||||||
toast({
|
toast({
|
||||||
title: "Error",
|
title: "Error",
|
||||||
description: error.message || "Failed to update setting",
|
description: error.message || "Failed to update setting",
|
||||||
@@ -74,17 +75,17 @@ export function useAdminSettings() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const getSettingValue = useCallback((key: string, defaultValue: any = null) => {
|
const getSettingValue = useCallback((key: string, defaultValue: unknown = null) => {
|
||||||
return settingsMap[key] ?? defaultValue;
|
return settingsMap[key] ?? defaultValue;
|
||||||
}, [settingsMap]);
|
}, [settingsMap]);
|
||||||
|
|
||||||
const updateSetting = async (key: string, value: any) => {
|
const updateSetting = async (key: string, value: unknown) => {
|
||||||
return updateSettingMutation.mutateAsync({ key, value });
|
return updateSettingMutation.mutateAsync({ key, value });
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper functions for common settings (memoized with useCallback for stable references)
|
// Helper functions for common settings (memoized with useCallback for stable references)
|
||||||
const getAutoFlagThreshold = useCallback(() => {
|
const getAutoFlagThreshold = useCallback(() => {
|
||||||
return parseInt(getSettingValue('moderation.auto_flag_threshold', '3'));
|
return parseInt(String(getSettingValue('moderation.auto_flag_threshold', '3')));
|
||||||
}, [getSettingValue]);
|
}, [getSettingValue]);
|
||||||
|
|
||||||
const getRequireApproval = useCallback(() => {
|
const getRequireApproval = useCallback(() => {
|
||||||
@@ -94,7 +95,7 @@ export function useAdminSettings() {
|
|||||||
|
|
||||||
const getBanDurations = useCallback(() => {
|
const getBanDurations = useCallback(() => {
|
||||||
const value = getSettingValue('moderation.ban_durations', ['1d', '7d', '30d', 'permanent']);
|
const value = getSettingValue('moderation.ban_durations', ['1d', '7d', '30d', 'permanent']);
|
||||||
return Array.isArray(value) ? value : JSON.parse(value || '[]');
|
return Array.isArray(value) ? value : JSON.parse(String(value || '[]'));
|
||||||
}, [getSettingValue]);
|
}, [getSettingValue]);
|
||||||
|
|
||||||
const getEmailAlertsEnabled = useCallback(() => {
|
const getEmailAlertsEnabled = useCallback(() => {
|
||||||
@@ -103,11 +104,11 @@ export function useAdminSettings() {
|
|||||||
}, [getSettingValue]);
|
}, [getSettingValue]);
|
||||||
|
|
||||||
const getReportThreshold = useCallback(() => {
|
const getReportThreshold = useCallback(() => {
|
||||||
return parseInt(getSettingValue('notifications.report_threshold', '5'));
|
return parseInt(String(getSettingValue('notifications.report_threshold', '5')));
|
||||||
}, [getSettingValue]);
|
}, [getSettingValue]);
|
||||||
|
|
||||||
const getAuditRetentionDays = useCallback(() => {
|
const getAuditRetentionDays = useCallback(() => {
|
||||||
return parseInt(getSettingValue('system.audit_retention_days', '365'));
|
return parseInt(String(getSettingValue('system.audit_retention_days', '365')));
|
||||||
}, [getSettingValue]);
|
}, [getSettingValue]);
|
||||||
|
|
||||||
const getAutoCleanupEnabled = useCallback(() => {
|
const getAutoCleanupEnabled = useCallback(() => {
|
||||||
@@ -115,10 +116,11 @@ export function useAdminSettings() {
|
|||||||
return value === true || value === 'true';
|
return value === true || value === 'true';
|
||||||
}, [getSettingValue]);
|
}, [getSettingValue]);
|
||||||
|
|
||||||
const getAdminPanelRefreshMode = useCallback(() => {
|
const getAdminPanelRefreshMode = useCallback((): 'auto' | 'manual' => {
|
||||||
const value = getSettingValue('system.admin_panel_refresh_mode', 'auto');
|
const value = getSettingValue('system.admin_panel_refresh_mode', 'auto');
|
||||||
// Remove quotes if they exist (JSON string stored in DB)
|
// Remove quotes if they exist (JSON string stored in DB)
|
||||||
return typeof value === 'string' ? value.replace(/"/g, '') : value;
|
const cleanValue = typeof value === 'string' ? value.replace(/"/g, '') : String(value);
|
||||||
|
return (cleanValue === 'manual' ? 'manual' : 'auto') as 'auto' | 'manual';
|
||||||
}, [getSettingValue]);
|
}, [getSettingValue]);
|
||||||
|
|
||||||
const getAdminPanelPollInterval = useCallback(() => {
|
const getAdminPanelPollInterval = useCallback(() => {
|
||||||
|
|||||||
@@ -1137,7 +1137,7 @@ export async function rejectSubmissionItems(
|
|||||||
.eq('id', itemId);
|
.eq('id', itemId);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error(`Error rejecting item ${itemId}:`, error);
|
logger.error('Error rejecting item', { error, itemId });
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1171,7 +1171,7 @@ async function updateSubmissionStatusAfterRejection(submissionId: string): Promi
|
|||||||
.eq('submission_id', submissionId);
|
.eq('submission_id', submissionId);
|
||||||
|
|
||||||
if (fetchError) {
|
if (fetchError) {
|
||||||
console.error('Error fetching submission items:', fetchError);
|
logger.error('Error fetching submission items', { error: fetchError, submissionId });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1202,7 +1202,7 @@ async function updateSubmissionStatusAfterRejection(submissionId: string): Promi
|
|||||||
.eq('id', submissionId);
|
.eq('id', submissionId);
|
||||||
|
|
||||||
if (updateError) {
|
if (updateError) {
|
||||||
console.error('Error updating submission status:', updateError);
|
logger.error('Error updating submission status', { error: updateError, submissionId });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -85,11 +85,15 @@ Deno.serve(async (req) => {
|
|||||||
.rpc('cancel_user_email_change', { _user_id: userId });
|
.rpc('cancel_user_email_change', { _user_id: userId });
|
||||||
|
|
||||||
if (cancelError || !cancelled) {
|
if (cancelError || !cancelled) {
|
||||||
console.error('Error cancelling email change:', cancelError);
|
edgeLogger.error('Error cancelling email change', {
|
||||||
|
error: cancelError?.message,
|
||||||
|
userId,
|
||||||
|
requestId: tracking.requestId
|
||||||
|
});
|
||||||
throw new Error('Unable to cancel email change: ' + (cancelError?.message || 'Unknown error'));
|
throw new Error('Unable to cancel email change: ' + (cancelError?.message || 'Unknown error'));
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Successfully cancelled email change for user ${userId}`);
|
edgeLogger.info('Successfully cancelled email change', { userId, requestId: tracking.requestId });
|
||||||
|
|
||||||
// Log the cancellation in admin_audit_log
|
// Log the cancellation in admin_audit_log
|
||||||
const { error: auditError } = await supabaseAdmin
|
const { error: auditError } = await supabaseAdmin
|
||||||
@@ -104,7 +108,10 @@ Deno.serve(async (req) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (auditError) {
|
if (auditError) {
|
||||||
console.error('Error logging audit:', auditError);
|
edgeLogger.error('Error logging audit', {
|
||||||
|
error: auditError.message,
|
||||||
|
requestId: tracking.requestId
|
||||||
|
});
|
||||||
// Don't fail the request if audit logging fails
|
// Don't fail the request if audit logging fails
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';
|
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';
|
||||||
|
import { edgeLogger } from '../_shared/logger.ts';
|
||||||
|
|
||||||
const corsHeaders = {
|
const corsHeaders = {
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
@@ -38,7 +39,7 @@ Deno.serve(async (req) => {
|
|||||||
errors: [],
|
errors: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('Starting version cleanup job...');
|
edgeLogger.info('Starting version cleanup job');
|
||||||
|
|
||||||
// Get retention settings from admin_settings
|
// Get retention settings from admin_settings
|
||||||
const { data: retentionSetting, error: settingsError } = await supabaseClient
|
const { data: retentionSetting, error: settingsError } = await supabaseClient
|
||||||
@@ -55,7 +56,7 @@ Deno.serve(async (req) => {
|
|||||||
const cutoffDate = new Date();
|
const cutoffDate = new Date();
|
||||||
cutoffDate.setDate(cutoffDate.getDate() - retentionDays);
|
cutoffDate.setDate(cutoffDate.getDate() - retentionDays);
|
||||||
|
|
||||||
console.log(`Cleanup configuration: ${retentionDays} day retention, cutoff: ${cutoffDate.toISOString()}`);
|
edgeLogger.info('Cleanup configuration', { retentionDays, cutoff: cutoffDate.toISOString() });
|
||||||
|
|
||||||
// Step 1: Delete orphaned edit history (where submission_item no longer exists)
|
// Step 1: Delete orphaned edit history (where submission_item no longer exists)
|
||||||
const { data: orphanedRecords, error: orphanError } = await supabaseClient
|
const { data: orphanedRecords, error: orphanError } = await supabaseClient
|
||||||
@@ -63,10 +64,10 @@ Deno.serve(async (req) => {
|
|||||||
|
|
||||||
if (orphanError) {
|
if (orphanError) {
|
||||||
stats.errors.push(`Failed to find orphaned records: ${orphanError.message}`);
|
stats.errors.push(`Failed to find orphaned records: ${orphanError.message}`);
|
||||||
console.error('Orphan detection error:', orphanError);
|
edgeLogger.error('Orphan detection error', { error: orphanError.message });
|
||||||
} else if (orphanedRecords && orphanedRecords.length > 0) {
|
} else if (orphanedRecords && orphanedRecords.length > 0) {
|
||||||
const orphanedIds = orphanedRecords.map((r: any) => r.id);
|
const orphanedIds = orphanedRecords.map((r: { id: string }) => r.id);
|
||||||
console.log(`Found ${orphanedIds.length} orphaned edit history records`);
|
edgeLogger.info('Found orphaned edit history records', { count: orphanedIds.length });
|
||||||
|
|
||||||
const { error: deleteOrphanError } = await supabaseClient
|
const { error: deleteOrphanError } = await supabaseClient
|
||||||
.from('item_edit_history')
|
.from('item_edit_history')
|
||||||
@@ -75,10 +76,10 @@ Deno.serve(async (req) => {
|
|||||||
|
|
||||||
if (deleteOrphanError) {
|
if (deleteOrphanError) {
|
||||||
stats.errors.push(`Failed to delete orphaned records: ${deleteOrphanError.message}`);
|
stats.errors.push(`Failed to delete orphaned records: ${deleteOrphanError.message}`);
|
||||||
console.error('Orphan deletion error:', deleteOrphanError);
|
edgeLogger.error('Orphan deletion error', { error: deleteOrphanError.message });
|
||||||
} else {
|
} else {
|
||||||
stats.orphaned_records_deleted = orphanedIds.length;
|
stats.orphaned_records_deleted = orphanedIds.length;
|
||||||
console.log(`Deleted ${orphanedIds.length} orphaned records`);
|
edgeLogger.info('Deleted orphaned records', { count: orphanedIds.length });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,7 +93,7 @@ Deno.serve(async (req) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (items && items.length > 0) {
|
if (items && items.length > 0) {
|
||||||
console.log(`Processing ${items.length} submission items for version cleanup`);
|
edgeLogger.info('Processing submission items for version cleanup', { itemCount: items.length });
|
||||||
|
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
try {
|
try {
|
||||||
@@ -165,7 +166,7 @@ Deno.serve(async (req) => {
|
|||||||
|
|
||||||
stats.processing_time_ms = Date.now() - startTime;
|
stats.processing_time_ms = Date.now() - startTime;
|
||||||
|
|
||||||
console.log('Cleanup completed successfully:', {
|
edgeLogger.info('Cleanup completed successfully', {
|
||||||
...stats,
|
...stats,
|
||||||
errors: stats.errors.length > 0 ? stats.errors : undefined,
|
errors: stats.errors.length > 0 ? stats.errors : undefined,
|
||||||
});
|
});
|
||||||
@@ -182,7 +183,7 @@ Deno.serve(async (req) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Cleanup job failed:', error);
|
edgeLogger.error('Cleanup job failed', { error: error instanceof Error ? error.message : String(error) });
|
||||||
|
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
|
|||||||
@@ -96,7 +96,10 @@ serve(async (req) => {
|
|||||||
throw new Error('Confirmation code has expired. Please request a new deletion code.');
|
throw new Error('Confirmation code has expired. Please request a new deletion code.');
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Deactivating account and confirming deletion request...');
|
edgeLogger.info('Deactivating account and confirming deletion request', {
|
||||||
|
userId: user.id,
|
||||||
|
requestId: tracking.requestId
|
||||||
|
});
|
||||||
|
|
||||||
// Deactivate profile
|
// Deactivate profile
|
||||||
const { error: profileError } = await supabaseClient
|
const { error: profileError } = await supabaseClient
|
||||||
@@ -109,7 +112,11 @@ serve(async (req) => {
|
|||||||
.eq('user_id', user.id);
|
.eq('user_id', user.id);
|
||||||
|
|
||||||
if (profileError) {
|
if (profileError) {
|
||||||
console.error('Error deactivating profile:', profileError);
|
edgeLogger.error('Error deactivating profile', {
|
||||||
|
error: profileError.message,
|
||||||
|
userId: user.id,
|
||||||
|
requestId: tracking.requestId
|
||||||
|
});
|
||||||
throw profileError;
|
throw profileError;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,7 +129,10 @@ serve(async (req) => {
|
|||||||
.eq('id', deletionRequest.id);
|
.eq('id', deletionRequest.id);
|
||||||
|
|
||||||
if (updateError) {
|
if (updateError) {
|
||||||
console.error('Error updating deletion request:', updateError);
|
edgeLogger.error('Error updating deletion request', {
|
||||||
|
error: updateError.message,
|
||||||
|
requestId: tracking.requestId
|
||||||
|
});
|
||||||
throw updateError;
|
throw updateError;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,9 +170,12 @@ serve(async (req) => {
|
|||||||
`,
|
`,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
console.log('Deletion confirmation email sent');
|
edgeLogger.info('Deletion confirmation email sent', { requestId: tracking.requestId });
|
||||||
} catch (emailError) {
|
} catch (emailError) {
|
||||||
console.error('Failed to send email:', emailError);
|
edgeLogger.error('Failed to send email', {
|
||||||
|
error: emailError instanceof Error ? emailError.message : String(emailError),
|
||||||
|
requestId: tracking.requestId
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
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 { Novu } from "npm:@novu/api@1.6.0";
|
import { Novu } from "npm:@novu/api@1.6.0";
|
||||||
|
import { edgeLogger } from '../_shared/logger.ts';
|
||||||
|
|
||||||
const corsHeaders = {
|
const corsHeaders = {
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
@@ -189,7 +190,7 @@ serve(async (req) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Creating Novu subscriber:', { subscriberId, email, firstName });
|
edgeLogger.info('Creating Novu subscriber', { subscriberId, email: '***', firstName, requestId: tracking.requestId });
|
||||||
|
|
||||||
const subscriber = await novu.subscribers.identify(subscriberId, {
|
const subscriber = await novu.subscribers.identify(subscriberId, {
|
||||||
email,
|
email,
|
||||||
@@ -201,18 +202,26 @@ serve(async (req) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const duration = endRequest(tracking);
|
const duration = endRequest(tracking);
|
||||||
console.log('Subscriber created successfully:', subscriber.data, { requestId: tracking.requestId, duration });
|
edgeLogger.info('Subscriber created successfully', {
|
||||||
|
subscriberId: subscriber.data._id,
|
||||||
|
requestId: tracking.requestId,
|
||||||
|
duration
|
||||||
|
});
|
||||||
|
|
||||||
// Add subscriber to "users" topic for global announcements
|
// Add subscriber to "users" topic for global announcements
|
||||||
try {
|
try {
|
||||||
console.log('Adding subscriber to "users" topic...', { subscriberId });
|
edgeLogger.info('Adding subscriber to users topic', { subscriberId, requestId: tracking.requestId });
|
||||||
await novu.topics.addSubscribers('users', {
|
await novu.topics.addSubscribers('users', {
|
||||||
subscribers: [subscriberId],
|
subscribers: [subscriberId],
|
||||||
});
|
});
|
||||||
console.log('Successfully added subscriber to "users" topic', { subscriberId });
|
edgeLogger.info('Successfully added subscriber to users topic', { subscriberId, requestId: tracking.requestId });
|
||||||
} catch (topicError: any) {
|
} catch (topicError: unknown) {
|
||||||
// Non-blocking - log error but don't fail the request
|
// Non-blocking - log error but don't fail the request
|
||||||
console.error('Failed to add subscriber to "users" topic:', topicError.message, { subscriberId });
|
edgeLogger.error('Failed to add subscriber to users topic', {
|
||||||
|
error: topicError instanceof Error ? topicError.message : String(topicError),
|
||||||
|
subscriberId,
|
||||||
|
requestId: tracking.requestId
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Response(
|
return new Response(
|
||||||
@@ -226,9 +235,13 @@ serve(async (req) => {
|
|||||||
status: 200,
|
status: 200,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (error: any) {
|
} catch (error: unknown) {
|
||||||
const duration = endRequest(tracking);
|
const duration = endRequest(tracking);
|
||||||
console.error('Error creating Novu subscriber:', error, { requestId: tracking.requestId, duration });
|
edgeLogger.error('Error creating Novu subscriber', {
|
||||||
|
error: error instanceof Error ? error.message : String(error),
|
||||||
|
requestId: tracking.requestId,
|
||||||
|
duration
|
||||||
|
});
|
||||||
|
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
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";
|
import { edgeLogger, startRequest, endRequest } from "../_shared/logger.ts";
|
||||||
|
|
||||||
const corsHeaders = {
|
const corsHeaders = {
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
@@ -36,14 +36,8 @@ function cleanupExpiredEntries() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log cleanup activity for monitoring
|
// Cleanup runs silently unless there are issues
|
||||||
if (deletedCount > 0) {
|
|
||||||
console.log(`[Cleanup] Removed ${deletedCount} expired entries. Map size: ${mapSizeBefore} -> ${rateLimitMap.size}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset failure count on successful cleanup
|
|
||||||
if (cleanupFailureCount > 0) {
|
if (cleanupFailureCount > 0) {
|
||||||
console.log(`[Cleanup] Successful cleanup after ${cleanupFailureCount} previous failures. Resetting failure count.`);
|
|
||||||
cleanupFailureCount = 0;
|
cleanupFailureCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,20 +46,20 @@ function cleanupExpiredEntries() {
|
|||||||
cleanupFailureCount++;
|
cleanupFailureCount++;
|
||||||
|
|
||||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
const errorStack = error instanceof Error ? error.stack : 'No stack trace available';
|
|
||||||
|
|
||||||
console.error('[Cleanup Error]', {
|
edgeLogger.error('Cleanup error', {
|
||||||
attempt: cleanupFailureCount,
|
attempt: cleanupFailureCount,
|
||||||
maxAttempts: MAX_CLEANUP_FAILURES,
|
maxAttempts: MAX_CLEANUP_FAILURES,
|
||||||
error: errorMessage,
|
error: errorMessage,
|
||||||
stack: errorStack,
|
|
||||||
mapSize: rateLimitMap.size
|
mapSize: rateLimitMap.size
|
||||||
});
|
});
|
||||||
|
|
||||||
// FALLBACK MECHANISM: If cleanup fails repeatedly, force clear to prevent memory leak
|
// FALLBACK MECHANISM: If cleanup fails repeatedly, force clear to prevent memory leak
|
||||||
if (cleanupFailureCount >= MAX_CLEANUP_FAILURES) {
|
if (cleanupFailureCount >= MAX_CLEANUP_FAILURES) {
|
||||||
console.error(`[Cleanup CRITICAL] Cleanup has failed ${cleanupFailureCount} times consecutively!`);
|
edgeLogger.error('Cleanup critical - forcing emergency cleanup', {
|
||||||
console.error(`[Cleanup CRITICAL] Forcing emergency cleanup to prevent memory leak...`);
|
consecutiveFailures: cleanupFailureCount,
|
||||||
|
mapSize: rateLimitMap.size
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Emergency: Clear oldest 50% of entries to prevent unbounded growth
|
// Emergency: Clear oldest 50% of entries to prevent unbounded growth
|
||||||
@@ -79,20 +73,20 @@ function cleanupExpiredEntries() {
|
|||||||
clearedCount++;
|
clearedCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.warn(`[Cleanup CRITICAL] Emergency cleanup completed. Cleared ${clearedCount} entries. Map size: ${rateLimitMap.size}`);
|
edgeLogger.warn('Emergency cleanup completed', { clearedCount, newMapSize: rateLimitMap.size });
|
||||||
|
|
||||||
// Reset failure count after emergency cleanup
|
// Reset failure count after emergency cleanup
|
||||||
cleanupFailureCount = 0;
|
cleanupFailureCount = 0;
|
||||||
|
|
||||||
} catch (emergencyError) {
|
} catch (emergencyError) {
|
||||||
// Last resort: If even emergency cleanup fails, clear everything
|
// Last resort: If even emergency cleanup fails, clear everything
|
||||||
console.error(`[Cleanup CRITICAL] Emergency cleanup failed! Clearing entire rate limit map.`);
|
|
||||||
console.error(`[Cleanup CRITICAL] Emergency error: ${emergencyError}`);
|
|
||||||
|
|
||||||
const originalSize = rateLimitMap.size;
|
const originalSize = rateLimitMap.size;
|
||||||
rateLimitMap.clear();
|
rateLimitMap.clear();
|
||||||
|
|
||||||
console.warn(`[Cleanup CRITICAL] Cleared entire rate limit map (${originalSize} entries) to prevent memory leak.`);
|
edgeLogger.error('Emergency cleanup failed - cleared entire map', {
|
||||||
|
originalSize,
|
||||||
|
error: emergencyError instanceof Error ? emergencyError.message : String(emergencyError)
|
||||||
|
});
|
||||||
cleanupFailureCount = 0;
|
cleanupFailureCount = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,7 +96,6 @@ function cleanupExpiredEntries() {
|
|||||||
// Reset cleanup failure count periodically to avoid permanent emergency state
|
// Reset cleanup failure count periodically to avoid permanent emergency state
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
if (cleanupFailureCount > 0) {
|
if (cleanupFailureCount > 0) {
|
||||||
console.warn(`[Cleanup] Resetting failure count (was ${cleanupFailureCount}) after timeout period.`);
|
|
||||||
cleanupFailureCount = 0;
|
cleanupFailureCount = 0;
|
||||||
}
|
}
|
||||||
}, CLEANUP_FAILURE_RESET_INTERVAL);
|
}, CLEANUP_FAILURE_RESET_INTERVAL);
|
||||||
@@ -140,11 +133,17 @@ function checkRateLimit(ip: string): { allowed: boolean; retryAfter?: number } {
|
|||||||
deletedCount++;
|
deletedCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.warn(`[Rate Limit] Map reached ${MAX_MAP_SIZE} entries. Cleared ${deletedCount} oldest entries. New size: ${rateLimitMap.size}`);
|
edgeLogger.warn('Rate limit map at capacity - evicted entries', {
|
||||||
|
maxSize: MAX_MAP_SIZE,
|
||||||
|
deletedCount,
|
||||||
|
newSize: rateLimitMap.size
|
||||||
|
});
|
||||||
} catch (evictionError) {
|
} catch (evictionError) {
|
||||||
// CRITICAL: LRU eviction failed - log error and attempt emergency clear
|
// CRITICAL: LRU eviction failed - log error and attempt emergency clear
|
||||||
console.error(`[Rate Limit CRITICAL] LRU eviction failed! Error: ${evictionError}`);
|
edgeLogger.error('LRU eviction failed', {
|
||||||
console.error(`[Rate Limit CRITICAL] Map size: ${rateLimitMap.size}`);
|
error: evictionError instanceof Error ? evictionError.message : String(evictionError),
|
||||||
|
mapSize: rateLimitMap.size
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Emergency: Clear first 30% of entries without sorting
|
// Emergency: Clear first 30% of entries without sorting
|
||||||
@@ -158,9 +157,14 @@ function checkRateLimit(ip: string): { allowed: boolean; retryAfter?: number } {
|
|||||||
|
|
||||||
keysToDelete.forEach(key => rateLimitMap.delete(key));
|
keysToDelete.forEach(key => rateLimitMap.delete(key));
|
||||||
|
|
||||||
console.warn(`[Rate Limit CRITICAL] Emergency eviction cleared ${keysToDelete.length} entries. New size: ${rateLimitMap.size}`);
|
edgeLogger.warn('Emergency eviction completed', {
|
||||||
|
clearedCount: keysToDelete.length,
|
||||||
|
newSize: rateLimitMap.size
|
||||||
|
});
|
||||||
} catch (emergencyError) {
|
} catch (emergencyError) {
|
||||||
console.error(`[Rate Limit CRITICAL] Emergency eviction also failed! Clearing entire map. Error: ${emergencyError}`);
|
edgeLogger.error('Emergency eviction failed - clearing entire map', {
|
||||||
|
error: emergencyError instanceof Error ? emergencyError.message : String(emergencyError)
|
||||||
|
});
|
||||||
rateLimitMap.clear();
|
rateLimitMap.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -211,7 +215,7 @@ serve(async (req) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PII Note: Do not log full IP addresses in production
|
// PII Note: Do not log full IP addresses in production
|
||||||
console.log('[Location] Detecting location for request');
|
edgeLogger.info('Detecting location for request', { requestId: tracking.requestId });
|
||||||
|
|
||||||
// Use configurable geolocation service with proper error handling
|
// Use configurable geolocation service with proper error handling
|
||||||
// Defaults to ip-api.com if not configured
|
// Defaults to ip-api.com if not configured
|
||||||
@@ -222,7 +226,10 @@ serve(async (req) => {
|
|||||||
try {
|
try {
|
||||||
geoResponse = await fetch(`${geoApiUrl}/${clientIP}?fields=${geoApiFields}`);
|
geoResponse = await fetch(`${geoApiUrl}/${clientIP}?fields=${geoApiFields}`);
|
||||||
} catch (fetchError) {
|
} catch (fetchError) {
|
||||||
console.error('Network error fetching location data:', fetchError);
|
edgeLogger.error('Network error fetching location data', {
|
||||||
|
error: fetchError instanceof Error ? fetchError.message : String(fetchError),
|
||||||
|
requestId: tracking.requestId
|
||||||
|
});
|
||||||
throw new Error('Network error: Unable to reach geolocation service');
|
throw new Error('Network error: Unable to reach geolocation service');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,7 +241,10 @@ serve(async (req) => {
|
|||||||
try {
|
try {
|
||||||
geoData = await geoResponse.json();
|
geoData = await geoResponse.json();
|
||||||
} catch (parseError) {
|
} catch (parseError) {
|
||||||
console.error('Failed to parse geolocation response:', parseError);
|
edgeLogger.error('Failed to parse geolocation response', {
|
||||||
|
error: parseError instanceof Error ? parseError.message : String(parseError),
|
||||||
|
requestId: tracking.requestId
|
||||||
|
});
|
||||||
throw new Error('Invalid response format from geolocation service');
|
throw new Error('Invalid response format from geolocation service');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,7 +262,7 @@ serve(async (req) => {
|
|||||||
measurementSystem
|
measurementSystem
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('[Location] Location detected:', {
|
edgeLogger.info('Location detected', {
|
||||||
country: result.country,
|
country: result.country,
|
||||||
countryCode: result.countryCode,
|
countryCode: result.countryCode,
|
||||||
measurementSystem: result.measurementSystem,
|
measurementSystem: result.measurementSystem,
|
||||||
@@ -275,12 +285,9 @@ serve(async (req) => {
|
|||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
// Enhanced error logging for better visibility and debugging
|
// Enhanced error logging for better visibility and debugging
|
||||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
const errorStack = error instanceof Error ? error.stack : 'No stack trace available';
|
|
||||||
|
|
||||||
console.error('[Location Detection Error]', {
|
edgeLogger.error('Location detection error', {
|
||||||
error: errorMessage,
|
error: errorMessage,
|
||||||
stack: errorStack,
|
|
||||||
hasIP: true, // IP removed for PII protection
|
|
||||||
requestId: tracking.requestId
|
requestId: tracking.requestId
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user