Fix remaining console statements

This commit is contained in:
gpt-engineer-app[bot]
2025-11-03 19:24:38 +00:00
parent ba6bb8a317
commit 99ceacfe0c
12 changed files with 225 additions and 118 deletions

View File

@@ -2,8 +2,15 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts'; import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts';
import { AlertCircle, TrendingUp, Users, Zap } from 'lucide-react'; import { AlertCircle, TrendingUp, Users, Zap } from 'lucide-react';
interface ErrorSummary {
error_type: string | null;
occurrence_count: number | null;
affected_users: number | null;
avg_duration_ms: number | null;
}
interface ErrorAnalyticsProps { interface ErrorAnalyticsProps {
errorSummary: any[] | undefined; errorSummary: ErrorSummary[] | undefined;
} }
export function ErrorAnalytics({ errorSummary }: ErrorAnalyticsProps) { export function ErrorAnalytics({ errorSummary }: ErrorAnalyticsProps) {
@@ -11,8 +18,8 @@ export function ErrorAnalytics({ errorSummary }: ErrorAnalyticsProps) {
return null; return null;
} }
const totalErrors = errorSummary.reduce((sum, item) => sum + item.occurrence_count, 0); const totalErrors = errorSummary.reduce((sum, item) => sum + (item.occurrence_count || 0), 0);
const totalAffectedUsers = errorSummary.reduce((sum, item) => sum + item.affected_users, 0); const totalAffectedUsers = errorSummary.reduce((sum, item) => sum + (item.affected_users || 0), 0);
const avgDuration = errorSummary.reduce((sum, item) => sum + (item.avg_duration_ms || 0), 0) / errorSummary.length; const avgDuration = errorSummary.reduce((sum, item) => sum + (item.avg_duration_ms || 0), 0) / errorSummary.length;
const topErrors = errorSummary.slice(0, 5); const topErrors = errorSummary.slice(0, 5);

View File

@@ -6,8 +6,31 @@ import { Copy, ExternalLink } from 'lucide-react';
import { format } from 'date-fns'; import { format } from 'date-fns';
import { toast } from 'sonner'; import { toast } from 'sonner';
interface Breadcrumb {
timestamp: string;
category: string;
message: string;
level: string;
data?: Record<string, unknown>;
}
interface ErrorDetails {
request_id: string;
created_at: string;
error_type: string;
error_message: string;
error_stack?: string;
endpoint: string;
method: string;
status_code: number;
duration_ms: number;
user_id?: string;
breadcrumbs?: Breadcrumb[];
environment_context?: Record<string, unknown>;
}
interface ErrorDetailsModalProps { interface ErrorDetailsModalProps {
error: any; error: ErrorDetails;
onClose: () => void; onClose: () => void;
} }
@@ -125,7 +148,7 @@ ${error.error_stack ? `Stack Trace:\n${error.error_stack}` : ''}
<TabsContent value="breadcrumbs"> <TabsContent value="breadcrumbs">
{error.breadcrumbs && error.breadcrumbs.length > 0 ? ( {error.breadcrumbs && error.breadcrumbs.length > 0 ? (
<div className="space-y-2"> <div className="space-y-2">
{error.breadcrumbs.map((crumb: any, index: number) => ( {error.breadcrumbs.map((crumb, index) => (
<div key={index} className="border-l-2 border-primary pl-4 py-2"> <div key={index} className="border-l-2 border-primary pl-4 py-2">
<div className="flex items-center gap-2 mb-1"> <div className="flex items-center gap-2 mb-1">
<Badge variant="outline" className="text-xs"> <Badge variant="outline" className="text-xs">

View File

@@ -18,19 +18,8 @@ interface EntityErrorBoundaryState {
errorInfo: ErrorInfo | null; errorInfo: ErrorInfo | null;
} }
/** type ErrorWithId = Error & { errorId: string };
* Entity Error Boundary Component (P0 #5)
*
* Specialized error boundary for entity detail pages.
* Prevents entity rendering errors from crashing the app.
*
* Usage:
* ```tsx
* <EntityErrorBoundary entityType="park" entitySlug={slug}>
* <ParkDetail />
* </EntityErrorBoundary>
* ```
*/
export class EntityErrorBoundary extends Component<EntityErrorBoundaryProps, EntityErrorBoundaryState> { export class EntityErrorBoundary extends Component<EntityErrorBoundaryProps, EntityErrorBoundaryState> {
constructor(props: EntityErrorBoundaryProps) { constructor(props: EntityErrorBoundaryProps) {
super(props); super(props);
@@ -61,7 +50,7 @@ export class EntityErrorBoundary extends Component<EntityErrorBoundaryProps, Ent
errorId, errorId,
}); });
this.setState({ errorInfo, error: { ...error, errorId } as any }); this.setState({ errorInfo, error: { ...error, errorId } as ErrorWithId });
} }
handleRetry = () => { handleRetry = () => {
@@ -131,9 +120,9 @@ export class EntityErrorBoundary extends Component<EntityErrorBoundaryProps, Ent
<p className="text-sm"> <p className="text-sm">
{this.state.error?.message || `An unexpected error occurred while loading this ${entityLabel.toLowerCase()}`} {this.state.error?.message || `An unexpected error occurred while loading this ${entityLabel.toLowerCase()}`}
</p> </p>
{(this.state.error as any)?.errorId && ( {(this.state.error as ErrorWithId)?.errorId && (
<p className="text-xs font-mono bg-destructive/10 px-2 py-1 rounded"> <p className="text-xs font-mono bg-destructive/10 px-2 py-1 rounded">
Reference ID: {((this.state.error as any).errorId as string).slice(0, 8)} Reference ID: {(this.state.error as ErrorWithId).errorId.slice(0, 8)}
</p> </p>
)} )}
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">

View File

@@ -18,19 +18,8 @@ interface ErrorBoundaryState {
errorInfo: ErrorInfo | null; errorInfo: ErrorInfo | null;
} }
/** type ErrorWithId = Error & { errorId: string };
* Generic Error Boundary Component (P0 #5)
*
* Prevents component errors from crashing the entire application.
* Shows user-friendly error UI with recovery options.
*
* Usage:
* ```tsx
* <ErrorBoundary context="PhotoUpload">
* <PhotoUploadForm />
* </ErrorBoundary>
* ```
*/
export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> { export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) { constructor(props: ErrorBoundaryProps) {
super(props); super(props);
@@ -61,7 +50,7 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
errorId, errorId,
}); });
this.setState({ errorInfo, error: { ...error, errorId } as any }); this.setState({ errorInfo, error: { ...error, errorId } as ErrorWithId });
this.props.onError?.(error, errorInfo); this.props.onError?.(error, errorInfo);
} }
@@ -105,9 +94,9 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
<p className="text-sm mt-2"> <p className="text-sm mt-2">
{this.state.error?.message || 'An unexpected error occurred'} {this.state.error?.message || 'An unexpected error occurred'}
</p> </p>
{(this.state.error as any)?.errorId && ( {(this.state.error as ErrorWithId)?.errorId && (
<p className="text-xs mt-2 font-mono bg-destructive/10 px-2 py-1 rounded"> <p className="text-xs mt-2 font-mono bg-destructive/10 px-2 py-1 rounded">
Reference ID: {((this.state.error as any).errorId as string).slice(0, 8)} Reference ID: {(this.state.error as ErrorWithId).errorId.slice(0, 8)}
</p> </p>
)} )}
</AlertDescription> </AlertDescription>

View File

@@ -13,19 +13,8 @@ interface RouteErrorBoundaryState {
error: Error | null; error: Error | null;
} }
/** type ErrorWithId = Error & { errorId: string };
* Route Error Boundary Component (P0 #5)
*
* Top-level error boundary that wraps all routes.
* Last line of defense to prevent complete app crashes.
*
* Usage: Wrap Routes component in App.tsx
* ```tsx
* <RouteErrorBoundary>
* <Routes>...</Routes>
* </RouteErrorBoundary>
* ```
*/
export class RouteErrorBoundary extends Component<RouteErrorBoundaryProps, RouteErrorBoundaryState> { export class RouteErrorBoundary extends Component<RouteErrorBoundaryProps, RouteErrorBoundaryState> {
constructor(props: RouteErrorBoundaryProps) { constructor(props: RouteErrorBoundaryProps) {
super(props); super(props);
@@ -56,7 +45,7 @@ export class RouteErrorBoundary extends Component<RouteErrorBoundaryProps, Route
errorId, errorId,
}); });
this.setState({ error: { ...error, errorId } as any }); this.setState({ error: { ...error, errorId } as ErrorWithId });
} }
handleReload = () => { handleReload = () => {
@@ -91,9 +80,9 @@ export class RouteErrorBoundary extends Component<RouteErrorBoundaryProps, Route
{this.state.error.message} {this.state.error.message}
</p> </p>
)} )}
{(this.state.error as any)?.errorId && ( {(this.state.error as ErrorWithId)?.errorId && (
<p className="text-xs font-mono bg-destructive/10 px-2 py-1 rounded"> <p className="text-xs font-mono bg-destructive/10 px-2 py-1 rounded">
Reference ID: {((this.state.error as any).errorId as string).slice(0, 8)} Reference ID: {(this.state.error as ErrorWithId).errorId.slice(0, 8)}
</p> </p>
)} )}
</div> </div>

View File

@@ -164,9 +164,12 @@ serve(async (req) => {
html: emailPayload.html, html: emailPayload.html,
}), }),
}); });
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', {
requestId: tracking.requestId,
error: emailError.message
});
} }
} }

View File

@@ -126,9 +126,12 @@ serve(async (req) => {
`, `,
}), }),
}); });
console.log('New confirmation code email sent'); edgeLogger.info('New confirmation code email sent', { requestId: tracking.requestId });
} catch (emailError) { } catch (emailError) {
console.error('Failed to send email:', emailError); edgeLogger.error('Failed to send email', {
requestId: tracking.requestId,
error: emailError.message
});
} }
} }

View File

@@ -1,4 +1,5 @@
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 { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts';
const corsHeaders = { const corsHeaders = {
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
@@ -89,7 +90,7 @@ async function registerTestEntity(
}); });
if (error && error.code !== '23505') { // Ignore unique constraint violations if (error && error.code !== '23505') { // Ignore unique constraint violations
console.error(`Error registering ${entityType} ${slug}:`, error); edgeLogger.error(`Error registering ${entityType} ${slug}`, { error: error.message, entityType, slug });
} }
} }
@@ -103,7 +104,7 @@ async function getExistingTestEntities(
.eq('entity_type', entityType); .eq('entity_type', entityType);
if (error) { if (error) {
console.error(`Error fetching existing ${entityType}:`, error); edgeLogger.error(`Error fetching existing ${entityType}`, { error: error.message, entityType });
return []; return [];
} }
@@ -121,7 +122,7 @@ async function getPendingSubmissionItems(
.eq('status', 'pending'); .eq('status', 'pending');
if (error) { if (error) {
console.error(`Error fetching pending ${itemType} items:`, error); edgeLogger.error(`Error fetching pending ${itemType} items`, { error: error.message, itemType });
return []; return [];
} }
@@ -129,6 +130,8 @@ async function getPendingSubmissionItems(
} }
Deno.serve(async (req) => { Deno.serve(async (req) => {
const tracking = startRequest();
if (req.method === 'OPTIONS') { if (req.method === 'OPTIONS') {
return new Response(null, { headers: corsHeaders }); return new Response(null, { headers: corsHeaders });
} }
@@ -176,12 +179,17 @@ Deno.serve(async (req) => {
stage stage
}: SeedOptions = await req.json(); }: SeedOptions = await req.json();
console.info('=== SEED DATA GENERATION DEBUG ==='); edgeLogger.info('Seed data generation started', {
console.info('Received entityTypes:', JSON.stringify(entityTypes)); requestId: tracking.requestId,
console.info('entityTypes is array:', Array.isArray(entityTypes)); entityTypes,
console.info('entityTypes length:', entityTypes?.length); preset,
console.info('Preset:', preset); fieldDensity,
console.info('==================================='); includeDependencies,
includeConflicts,
includeVersionChains,
includeEscalated,
includeExpiredLocks
});
const plan = PRESETS[preset]; const plan = PRESETS[preset];
if (!plan) { if (!plan) {
@@ -212,7 +220,7 @@ Deno.serve(async (req) => {
// Load existing test entities from registry if includeDependencies is true // Load existing test entities from registry if includeDependencies is true
if (includeDependencies) { if (includeDependencies) {
console.log('Loading existing test entities from registry...'); edgeLogger.info('Loading existing test entities from registry', { requestId: tracking.requestId });
const existingOperators = await getExistingTestEntities(supabase, 'operator'); const existingOperators = await getExistingTestEntities(supabase, 'operator');
const existingOwners = await getExistingTestEntities(supabase, 'property_owner'); const existingOwners = await getExistingTestEntities(supabase, 'property_owner');
@@ -260,8 +268,19 @@ Deno.serve(async (req) => {
pendingParks.forEach(item => createdSubmissionItems.park.push(item.id)); pendingParks.forEach(item => createdSubmissionItems.park.push(item.id));
pendingRideModels.forEach(item => createdSubmissionItems.ride_model.push(item.id)); pendingRideModels.forEach(item => createdSubmissionItems.ride_model.push(item.id));
console.log(`Loaded ${existingOperators.length} operators, ${existingOwners.length} owners, ${existingManufacturers.length} manufacturers, ${existingDesigners.length} designers, ${existingParks.length} parks`); edgeLogger.info('Loaded existing entities', {
console.log(`Loaded ${pendingOperators.length} pending operators, ${pendingOwners.length} pending owners, ${pendingManufacturers.length} pending manufacturers, ${pendingDesigners.length} pending designers, ${pendingParks.length} pending parks`); requestId: tracking.requestId,
operators: existingOperators.length,
owners: existingOwners.length,
manufacturers: existingManufacturers.length,
designers: existingDesigners.length,
parks: existingParks.length,
pendingOperators: pendingOperators.length,
pendingOwners: pendingOwners.length,
pendingManufacturers: pendingManufacturers.length,
pendingDesigners: pendingDesigners.length,
pendingParks: pendingParks.length
});
// Add pending parks' slugs to createdParks for ride generation // Add pending parks' slugs to createdParks for ride generation
pendingParks.forEach(item => { pendingParks.forEach(item => {
@@ -271,7 +290,7 @@ Deno.serve(async (req) => {
} }
}); });
console.log(`Total parks available for rides: ${createdParks.length}`); edgeLogger.info('Parks available for rides', { requestId: tracking.requestId, count: createdParks.length });
} }
// Helper to create submission // Helper to create submission
@@ -316,7 +335,7 @@ Deno.serve(async (req) => {
const { error: subError } = await supabase.from('content_submissions').insert(submissionData); const { error: subError } = await supabase.from('content_submissions').insert(submissionData);
if (subError) { if (subError) {
console.error(`Error inserting content_submission for type ${type}:`, subError); edgeLogger.error('Error inserting content_submission', { type, error: subError.message });
throw subError; throw subError;
} }
@@ -350,8 +369,7 @@ Deno.serve(async (req) => {
const { data: insertedData, error: typeError } = await supabase.from(table).insert(typeData).select('id').single(); const { data: insertedData, error: typeError } = await supabase.from(table).insert(typeData).select('id').single();
if (typeError) { if (typeError) {
console.error(`Error inserting into ${table} for type ${type}:`, typeError); edgeLogger.error('Error inserting into type table', { table, type, error: typeError.message, data: typeData });
console.error('Data being inserted:', JSON.stringify(typeData, null, 2));
throw typeError; throw typeError;
} }
return { submissionId, itemId, typeId: insertedData?.id }; return { submissionId, itemId, typeId: insertedData?.id };
@@ -373,10 +391,6 @@ Deno.serve(async (req) => {
// Create companies FIRST so parks can reference operators/owners // Create companies FIRST so parks can reference operators/owners
if (!stage || stage === 'companies') { if (!stage || stage === 'companies') {
console.info('=== COMPANY GENERATION CHECK ===');
console.info('Entity types array:', JSON.stringify(entityTypes));
console.info('Plan calls for companies:', plan.companies);
const companyTypes = ['manufacturer', 'operator', 'designer', 'property_owner']; const companyTypes = ['manufacturer', 'operator', 'designer', 'property_owner'];
// First, determine which company types are selected // First, determine which company types are selected
@@ -384,14 +398,17 @@ Deno.serve(async (req) => {
entityTypes.includes(pluralizeCompanyType(compType)) entityTypes.includes(pluralizeCompanyType(compType))
); );
console.info(`✓ Selected company types (${selectedCompanyTypes.length}):`, selectedCompanyTypes); edgeLogger.info('Company generation started', {
requestId: tracking.requestId,
entityTypes,
planCompanies: plan.companies,
selectedCompanyTypes
});
// Calculate fair distribution: base amount per type + extras // Calculate fair distribution: base amount per type + extras
const basePerType = Math.floor(plan.companies / selectedCompanyTypes.length); const basePerType = Math.floor(plan.companies / selectedCompanyTypes.length);
const extras = plan.companies % selectedCompanyTypes.length; const extras = plan.companies % selectedCompanyTypes.length;
console.info(`Distribution plan: ${basePerType} base per type, ${extras} extra(s) to first types`);
let companiesCreatedTotal = 0; let companiesCreatedTotal = 0;
for (let typeIndex = 0; typeIndex < selectedCompanyTypes.length; typeIndex++) { for (let typeIndex = 0; typeIndex < selectedCompanyTypes.length; typeIndex++) {
@@ -401,10 +418,9 @@ Deno.serve(async (req) => {
// Each type gets base amount, first N types get +1 extra // Each type gets base amount, first N types get +1 extra
const count = basePerType + (typeIndex < extras ? 1 : 0); const count = basePerType + (typeIndex < extras ? 1 : 0);
console.info(`✓ Generating ${count} companies of type ${compType}`); edgeLogger.info('Creating companies', { requestId: tracking.requestId, compType, count });
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
console.info(` Creating company ${i + 1}/${count} (type: ${compType})`);
const level = getPopulationLevel(fieldDensity, i); const level = getPopulationLevel(fieldDensity, i);
const companyData: any = { const companyData: any = {
name: `Test ${compType.replace('_', ' ')} ${i + 1}`, name: `Test ${compType.replace('_', ' ')} ${i + 1}`,
@@ -461,8 +477,8 @@ Deno.serve(async (req) => {
// Create parks // Create parks
if ((!stage || stage === 'parks') && entityTypes.includes('parks')) { if ((!stage || stage === 'parks') && entityTypes.includes('parks')) {
edgeLogger.info('Creating parks', { requestId: tracking.requestId, count: plan.parks });
for (let i = 0; i < plan.parks; i++) { for (let i = 0; i < plan.parks; i++) {
console.log(` Creating park ${i + 1}/${plan.parks}`);
const level = getPopulationLevel(fieldDensity, i); const level = getPopulationLevel(fieldDensity, i);
const shouldConflict = includeConflicts && createdParkSlugs.length > 0 && Math.random() < 0.15; const shouldConflict = includeConflicts && createdParkSlugs.length > 0 && Math.random() < 0.15;
const shouldVersionChain = includeVersionChains && createdParkSlugs.length > 0 && Math.random() < 0.15; const shouldVersionChain = includeVersionChains && createdParkSlugs.length > 0 && Math.random() < 0.15;
@@ -824,20 +840,33 @@ Deno.serve(async (req) => {
const executionTime = Date.now() - startTime; const executionTime = Date.now() - startTime;
edgeLogger.info('Seed data generation completed', {
requestId: tracking.requestId,
duration: executionTime,
summary,
stage: stage || 'all'
});
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
success: true, success: true,
summary, summary,
time: (executionTime / 1000).toFixed(2), time: (executionTime / 1000).toFixed(2),
stage: stage || 'all' stage: stage || 'all',
requestId: tracking.requestId
}), }),
{ headers: { ...corsHeaders, 'Content-Type': 'application/json' } } { headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
); );
} catch (error) { } catch (error) {
console.error('Seed error:', error); const duration = endRequest(tracking);
edgeLogger.error('Seed error', {
requestId: tracking.requestId,
duration,
error: error.message
});
return new Response( return new Response(
JSON.stringify({ error: error.message }), JSON.stringify({ error: error.message, requestId: tracking.requestId }),
{ status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } } { status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
); );
} }

View File

@@ -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 { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4"; import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4";
import { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts';
const corsHeaders = { const corsHeaders = {
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
@@ -12,10 +13,6 @@ interface EscalationRequest {
escalatedBy: string; escalatedBy: string;
} }
// Simple request tracking
const startRequest = () => ({ requestId: crypto.randomUUID(), start: Date.now() });
const endRequest = (tracking: { start: number }) => Date.now() - tracking.start;
serve(async (req) => { serve(async (req) => {
const tracking = startRequest(); const tracking = startRequest();
@@ -31,7 +28,12 @@ serve(async (req) => {
const { submissionId, escalationReason, escalatedBy }: EscalationRequest = await req.json(); const { submissionId, escalationReason, escalatedBy }: EscalationRequest = await req.json();
console.log('Processing escalation notification:', { submissionId, escalationReason, escalatedBy }); edgeLogger.info('Processing escalation notification', {
requestId: tracking.requestId,
submissionId,
escalatedBy,
action: 'send_escalation'
});
// Fetch submission details // Fetch submission details
const { data: submission, error: submissionError } = await supabase const { data: submission, error: submissionError } = await supabase
@@ -52,7 +54,11 @@ serve(async (req) => {
.single(); .single();
if (escalatorError) { if (escalatorError) {
console.error('Failed to fetch escalator profile:', escalatorError); edgeLogger.error('Failed to fetch escalator profile', {
requestId: tracking.requestId,
error: escalatorError.message,
escalatedBy
});
} }
// Fetch submission items count // Fetch submission items count
@@ -62,7 +68,11 @@ serve(async (req) => {
.eq('submission_id', submissionId); .eq('submission_id', submissionId);
if (countError) { if (countError) {
console.error('Failed to fetch items count:', countError); edgeLogger.error('Failed to fetch items count', {
requestId: tracking.requestId,
error: countError.message,
submissionId
});
} }
// Prepare email content // Prepare email content
@@ -185,7 +195,10 @@ serve(async (req) => {
}), }),
}); });
} catch (fetchError) { } catch (fetchError) {
console.error('Network error sending email:', fetchError); edgeLogger.error('Network error sending email', {
requestId: tracking.requestId,
error: fetchError.message
});
throw new Error('Network error: Unable to reach email service'); throw new Error('Network error: Unable to reach email service');
} }
@@ -196,7 +209,11 @@ serve(async (req) => {
} catch (parseError) { } catch (parseError) {
errorText = 'Unable to parse error response'; errorText = 'Unable to parse error response';
} }
console.error('ForwardEmail API error:', errorText); edgeLogger.error('ForwardEmail API error', {
requestId: tracking.requestId,
status: emailResponse.status,
errorText
});
throw new Error(`Failed to send email: ${emailResponse.status} - ${errorText}`); throw new Error(`Failed to send email: ${emailResponse.status} - ${errorText}`);
} }
@@ -204,10 +221,16 @@ serve(async (req) => {
try { try {
emailResult = await emailResponse.json(); emailResult = await emailResponse.json();
} catch (parseError) { } catch (parseError) {
console.error('Failed to parse email API response:', parseError); edgeLogger.error('Failed to parse email API response', {
requestId: tracking.requestId,
error: parseError.message
});
throw new Error('Invalid response from email service'); throw new Error('Invalid response from email service');
} }
console.log('Email sent successfully:', emailResult); edgeLogger.info('Email sent successfully', {
requestId: tracking.requestId,
emailId: emailResult.id
});
// Update submission with notification status // Update submission with notification status
const { error: updateError } = await supabase const { error: updateError } = await supabase
@@ -221,11 +244,20 @@ serve(async (req) => {
.eq('id', submissionId); .eq('id', submissionId);
if (updateError) { if (updateError) {
console.error('Failed to update submission escalation status:', updateError); edgeLogger.error('Failed to update submission escalation status', {
requestId: tracking.requestId,
error: updateError.message,
submissionId
});
} }
const duration = endRequest(tracking); const duration = endRequest(tracking);
console.log('Escalation notification sent', { requestId: tracking.requestId, duration, emailId: emailResult.id }); edgeLogger.info('Escalation notification sent', {
requestId: tracking.requestId,
duration,
emailId: emailResult.id,
submissionId
});
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
@@ -242,7 +274,11 @@ serve(async (req) => {
); );
} catch (error) { } catch (error) {
const duration = endRequest(tracking); const duration = endRequest(tracking);
console.error('Error in send-escalation-notification:', error, { requestId: tracking.requestId, duration }); edgeLogger.error('Error in send-escalation-notification', {
requestId: tracking.requestId,
duration,
error: error instanceof Error ? error.message : 'Unknown error'
});
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
error: error instanceof Error ? error.message : 'Unknown error occurred', error: error instanceof Error ? error.message : 'Unknown error occurred',

View File

@@ -138,7 +138,7 @@ serve(async (req) => {
const fromEmail = Deno.env.get('FROM_EMAIL_ADDRESS') || 'noreply@thrillwiki.com'; const fromEmail = Deno.env.get('FROM_EMAIL_ADDRESS') || 'noreply@thrillwiki.com';
if (!forwardEmailKey) { if (!forwardEmailKey) {
console.error('FORWARDEMAIL_API_KEY not configured'); edgeLogger.error('FORWARDEMAIL_API_KEY not configured', { requestId: tracking.requestId });
throw new Error('Email service not configured'); throw new Error('Email service not configured');
} }
@@ -158,7 +158,11 @@ serve(async (req) => {
if (!emailResponse.ok) { if (!emailResponse.ok) {
const errorText = await emailResponse.text(); const errorText = await emailResponse.text();
console.error('ForwardEmail API error:', errorText); edgeLogger.error('ForwardEmail API error', {
requestId: tracking.requestId,
status: emailResponse.status,
errorText
});
throw new Error(`Failed to send email: ${emailResponse.statusText}`); throw new Error(`Failed to send email: ${emailResponse.statusText}`);
} }

View File

@@ -1,6 +1,7 @@
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 { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts';
const corsHeaders = { const corsHeaders = {
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
@@ -12,10 +13,6 @@ const TOPICS = {
MODERATION_REPORTS: 'moderation-reports', MODERATION_REPORTS: 'moderation-reports',
} as const; } as const;
// Simple request tracking
const startRequest = () => ({ requestId: crypto.randomUUID(), start: Date.now() });
const endRequest = (tracking: { start: number }) => Date.now() - tracking.start;
serve(async (req) => { serve(async (req) => {
const tracking = startRequest(); const tracking = startRequest();
@@ -35,7 +32,10 @@ serve(async (req) => {
const novu = new Novu({ secretKey: novuApiKey }); const novu = new Novu({ secretKey: novuApiKey });
const supabase = createClient(supabaseUrl, supabaseServiceKey); const supabase = createClient(supabaseUrl, supabaseServiceKey);
console.log('Starting moderator sync to Novu topics...'); edgeLogger.info('Starting moderator sync to Novu topics', {
requestId: tracking.requestId,
action: 'sync_moderators'
});
// Get all moderators, admins, and superusers // Get all moderators, admins, and superusers
const { data: moderatorRoles, error: rolesError } = await supabase const { data: moderatorRoles, error: rolesError } = await supabase
@@ -50,7 +50,10 @@ serve(async (req) => {
// Get unique user IDs (a user might have multiple moderator-level roles) // Get unique user IDs (a user might have multiple moderator-level roles)
const uniqueUserIds = [...new Set(moderatorRoles.map(r => r.user_id))]; const uniqueUserIds = [...new Set(moderatorRoles.map(r => r.user_id))];
console.log(`Found ${uniqueUserIds.length} unique moderators to sync`); edgeLogger.info('Found unique moderators to sync', {
requestId: tracking.requestId,
count: uniqueUserIds.length
});
const topics = [TOPICS.MODERATION_SUBMISSIONS, TOPICS.MODERATION_REPORTS]; const topics = [TOPICS.MODERATION_SUBMISSIONS, TOPICS.MODERATION_REPORTS];
const results = { const results = {
@@ -62,11 +65,15 @@ serve(async (req) => {
try { try {
// Ensure topic exists (Novu will create it if it doesn't) // Ensure topic exists (Novu will create it if it doesn't)
await novu.topics.create({ key: topicKey, name: topicKey }); await novu.topics.create({ key: topicKey, name: topicKey });
console.log(`Topic ${topicKey} ready`); edgeLogger.info('Topic ready', { requestId: tracking.requestId, topicKey });
} catch (error: any) { } catch (error: any) {
// Topic might already exist, which is fine // Topic might already exist, which is fine
if (!error.message?.includes('already exists')) { if (!error.message?.includes('already exists')) {
console.warn(`Note about topic ${topicKey}:`, error.message); edgeLogger.warn('Note about topic', {
requestId: tracking.requestId,
topicKey,
error: error.message
});
} }
} }
@@ -83,10 +90,19 @@ serve(async (req) => {
subscribers: batch, subscribers: batch,
}); });
successCount += batch.length; successCount += batch.length;
console.log(`Added batch of ${batch.length} users to ${topicKey}`); edgeLogger.info('Added batch of users to topic', {
requestId: tracking.requestId,
topicKey,
batchSize: batch.length
});
} catch (error: any) { } catch (error: any) {
errorCount += batch.length; errorCount += batch.length;
console.error(`Error adding batch to ${topicKey}:`, error.message); edgeLogger.error('Error adding batch to topic', {
requestId: tracking.requestId,
topicKey,
batchSize: batch.length,
error: error.message
});
} }
} }
@@ -98,7 +114,11 @@ serve(async (req) => {
} }
const duration = endRequest(tracking); const duration = endRequest(tracking);
console.log('Sync completed:', results, { requestId: tracking.requestId, duration }); edgeLogger.info('Sync completed', {
requestId: tracking.requestId,
duration,
results
});
return new Response( return new Response(
JSON.stringify({ JSON.stringify({
@@ -118,7 +138,11 @@ serve(async (req) => {
); );
} catch (error: any) { } catch (error: any) {
const duration = endRequest(tracking); const duration = endRequest(tracking);
console.error('Error syncing moderators to topics:', error, { requestId: tracking.requestId, duration }); edgeLogger.error('Error syncing moderators to topics', {
requestId: tracking.requestId,
duration,
error: error.message
});
return new Response( return new Response(
JSON.stringify({ JSON.stringify({

View File

@@ -1,6 +1,6 @@
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"; import { edgeLogger, startRequest, endRequest } from "../_shared/logger.ts";
const corsHeaders = { const corsHeaders = {
'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Origin': '*',
@@ -42,7 +42,12 @@ serve(async (req) => {
? { subscriberId } ? { subscriberId }
: { topicKey: topicKey! }; : { topicKey: topicKey! };
console.log('Triggering notification:', { workflowId, recipient, requestId: tracking.requestId }); edgeLogger.info('Triggering notification', {
workflowId,
recipient,
requestId: tracking.requestId,
action: 'trigger_notification'
});
const result = await novu.trigger({ const result = await novu.trigger({
to: recipient, to: recipient,
@@ -51,7 +56,10 @@ serve(async (req) => {
overrides, overrides,
}); });
console.log('Notification triggered successfully:', result.data); edgeLogger.info('Notification triggered successfully', {
requestId: tracking.requestId,
transactionId: result.data.transactionId
});
endRequest(tracking, 200); endRequest(tracking, 200);
@@ -72,7 +80,10 @@ serve(async (req) => {
); );
} catch (error: unknown) { } catch (error: unknown) {
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); edgeLogger.error('Error triggering notification', {
requestId: tracking.requestId,
error: errorMessage
});
endRequest(tracking, 500, errorMessage); endRequest(tracking, 500, errorMessage);