diff --git a/src/hooks/moderation/useModerationActions.ts b/src/hooks/moderation/useModerationActions.ts index 31dbf39f..63563bec 100644 --- a/src/hooks/moderation/useModerationActions.ts +++ b/src/hooks/moderation/useModerationActions.ts @@ -86,7 +86,7 @@ export function useModerationActions(config: ModerationActionsConfig): Moderatio itemIds: string[], userId?: string, maxConflictRetries: number = 3, - timeoutMs: number = 30000 + timeoutMs: number = 60000 // Increased from 30s to 60s ): Promise<{ data: T | null; error: any; @@ -337,7 +337,7 @@ export function useModerationActions(config: ModerationActionsConfig): Moderatio submissionItems.map((i) => i.id), config.user?.id, 3, // Max 3 conflict retries - 30000 // 30s timeout + 60000 // 60s timeout (increased for slow queries) ); // Log retry attempts @@ -393,7 +393,7 @@ export function useModerationActions(config: ModerationActionsConfig): Moderatio submissionItems.map((i) => i.id), config.user?.id, 3, // Max 3 conflict retries - 30000 // 30s timeout + 60000 // 60s timeout (increased for slow queries) ); // Log retry attempts diff --git a/supabase/functions/process-selective-approval/index.ts b/supabase/functions/process-selective-approval/index.ts index 2e6420a8..47b3fd77 100644 --- a/supabase/functions/process-selective-approval/index.ts +++ b/supabase/functions/process-selective-approval/index.ts @@ -16,11 +16,42 @@ import { ValidationError } from '../_shared/typeValidation.ts'; const handler = async (req: Request, context: { supabase: any; user: any; span: any; requestId: string }) => { const { supabase, user, span: rootSpan, requestId } = context; + // Early logging - confirms request reached handler + addSpanEvent(rootSpan, 'handler_entry', { + requestId, + userId: user.id, + timestamp: Date.now() + }); + setSpanAttributes(rootSpan, { 'user.id': user.id, 'function.name': 'process-selective-approval' }); + // Health check endpoint + if (req.url.includes('/health')) { + addSpanEvent(rootSpan, 'health_check_start'); + const { data, error } = await supabase + .from('content_submissions') + .select('count') + .limit(1); + + addSpanEvent(rootSpan, 'health_check_complete', { + dbConnected: !error, + error: error?.message + }); + + return new Response(JSON.stringify({ + status: 'ok', + dbConnected: !error, + timestamp: Date.now(), + error: error?.message + }), { + headers: { 'Content-Type': 'application/json' }, + status: error ? 500 : 200 + }); + } + // STEP 1: Parse and validate request addSpanEvent(rootSpan, 'validation_start'); @@ -57,14 +88,37 @@ const handler = async (req: Request, context: { supabase: any; user: any; span: }); addSpanEvent(rootSpan, 'validation_complete'); - // STEP 2: Idempotency check + // STEP 2: Idempotency check with timeout addSpanEvent(rootSpan, 'idempotency_check_start'); - const { data: existingKey } = await supabase + + const idempotencyCheckPromise = supabase .from('submission_idempotency_keys') .select('*') .eq('idempotency_key', idempotencyKey) .single(); + // Add 5 second timeout for idempotency check + const timeoutPromise = new Promise((_, reject) => + setTimeout(() => reject(new Error('Idempotency check timed out after 5s')), 5000) + ); + + let existingKey; + try { + const result = await Promise.race([ + idempotencyCheckPromise, + timeoutPromise + ]) as any; + existingKey = result.data; + } catch (timeoutError: any) { + addSpanEvent(rootSpan, 'idempotency_check_timeout', { error: timeoutError.message }); + throw new Error(`Database query timeout: ${timeoutError.message}`); + } + + addSpanEvent(rootSpan, 'idempotency_check_complete', { + foundKey: !!existingKey, + status: existingKey?.status + }); + if (existingKey?.status === 'completed') { addSpanEvent(rootSpan, 'idempotency_cache_hit'); setSpanAttributes(rootSpan, { 'cache.hit': true });