mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 12:31:26 -05:00
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:
@@ -86,7 +86,7 @@ export function useModerationActions(config: ModerationActionsConfig): Moderatio
|
|||||||
itemIds: string[],
|
itemIds: string[],
|
||||||
userId?: string,
|
userId?: string,
|
||||||
maxConflictRetries: number = 3,
|
maxConflictRetries: number = 3,
|
||||||
timeoutMs: number = 30000
|
timeoutMs: number = 60000 // Increased from 30s to 60s
|
||||||
): Promise<{
|
): Promise<{
|
||||||
data: T | null;
|
data: T | null;
|
||||||
error: any;
|
error: any;
|
||||||
@@ -337,7 +337,7 @@ export function useModerationActions(config: ModerationActionsConfig): Moderatio
|
|||||||
submissionItems.map((i) => i.id),
|
submissionItems.map((i) => i.id),
|
||||||
config.user?.id,
|
config.user?.id,
|
||||||
3, // Max 3 conflict retries
|
3, // Max 3 conflict retries
|
||||||
30000 // 30s timeout
|
60000 // 60s timeout (increased for slow queries)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Log retry attempts
|
// Log retry attempts
|
||||||
@@ -393,7 +393,7 @@ export function useModerationActions(config: ModerationActionsConfig): Moderatio
|
|||||||
submissionItems.map((i) => i.id),
|
submissionItems.map((i) => i.id),
|
||||||
config.user?.id,
|
config.user?.id,
|
||||||
3, // Max 3 conflict retries
|
3, // Max 3 conflict retries
|
||||||
30000 // 30s timeout
|
60000 // 60s timeout (increased for slow queries)
|
||||||
);
|
);
|
||||||
|
|
||||||
// Log retry attempts
|
// Log retry attempts
|
||||||
|
|||||||
@@ -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 handler = async (req: Request, context: { supabase: any; user: any; span: any; requestId: string }) => {
|
||||||
const { supabase, user, span: rootSpan, requestId } = context;
|
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, {
|
setSpanAttributes(rootSpan, {
|
||||||
'user.id': user.id,
|
'user.id': user.id,
|
||||||
'function.name': 'process-selective-approval'
|
'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
|
// STEP 1: Parse and validate request
|
||||||
addSpanEvent(rootSpan, 'validation_start');
|
addSpanEvent(rootSpan, 'validation_start');
|
||||||
|
|
||||||
@@ -57,14 +88,37 @@ const handler = async (req: Request, context: { supabase: any; user: any; span:
|
|||||||
});
|
});
|
||||||
addSpanEvent(rootSpan, 'validation_complete');
|
addSpanEvent(rootSpan, 'validation_complete');
|
||||||
|
|
||||||
// STEP 2: Idempotency check
|
// STEP 2: Idempotency check with timeout
|
||||||
addSpanEvent(rootSpan, 'idempotency_check_start');
|
addSpanEvent(rootSpan, 'idempotency_check_start');
|
||||||
const { data: existingKey } = await supabase
|
|
||||||
|
const idempotencyCheckPromise = supabase
|
||||||
.from('submission_idempotency_keys')
|
.from('submission_idempotency_keys')
|
||||||
.select('*')
|
.select('*')
|
||||||
.eq('idempotency_key', idempotencyKey)
|
.eq('idempotency_key', idempotencyKey)
|
||||||
.single();
|
.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') {
|
if (existingKey?.status === 'completed') {
|
||||||
addSpanEvent(rootSpan, 'idempotency_cache_hit');
|
addSpanEvent(rootSpan, 'idempotency_cache_hit');
|
||||||
setSpanAttributes(rootSpan, { 'cache.hit': true });
|
setSpanAttributes(rootSpan, { 'cache.hit': true });
|
||||||
|
|||||||
Reference in New Issue
Block a user