Improve moderation edge flow and timeout handling

- Add early logging and health check to process-selective-approval edge function
- Implement idempotency check with timeout to avoid edge timeouts
- Expose health endpoint for connectivity diagnostics
- Increase client moderation action timeout from 30s to 60s
- Update moderation actions hook to accommodate longer timeouts
This commit is contained in:
gpt-engineer-app[bot]
2025-11-12 13:15:54 +00:00
parent 09c320f508
commit d18632c2b2
2 changed files with 59 additions and 5 deletions

View File

@@ -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

View File

@@ -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 });