feat: Implement comprehensive request tracking and state management

This commit is contained in:
gpt-engineer-app[bot]
2025-10-21 12:51:44 +00:00
parent 12433e49e3
commit 74860c6774
8 changed files with 400 additions and 77 deletions

View File

@@ -11,6 +11,7 @@ import { createTableQuery } from '@/lib/supabaseHelpers';
import type { ModerationItem } from '@/types/moderation';
import { logger } from '@/lib/logger';
import { getErrorMessage } from '@/lib/errorHandler';
import { invokeWithTracking, invokeBatchWithTracking } from '@/lib/edgeFunctionTracking';
/**
* Type-safe update data for review moderation
@@ -184,20 +185,32 @@ export async function approveSubmissionItems(
itemIds: string[]
): Promise<ModerationActionResult> {
try {
const { error: approvalError } = await supabase.functions.invoke(
const { data: approvalData, error: approvalError, requestId } = await invokeWithTracking(
'process-selective-approval',
{
body: {
itemIds,
submissionId,
},
itemIds,
submissionId,
}
);
if (approvalError) {
logger.error('Submission items approval failed via edge function', {
action: 'approve_submission_items',
submissionId,
itemCount: itemIds.length,
requestId,
error: approvalError.message,
});
throw new Error(`Failed to process submission items: ${approvalError.message}`);
}
logger.info('Submission items approved successfully', {
action: 'approve_submission_items',
submissionId,
itemCount: itemIds.length,
requestId,
});
return {
success: true,
message: `Successfully processed ${itemIds.length} item(s)`,
@@ -478,24 +491,28 @@ export async function deleteSubmission(
// Delete photos from Cloudflare
if (validImageIds.length > 0) {
const deletePromises = validImageIds.map(async imageId => {
try {
await supabase.functions.invoke('upload-image', {
method: 'DELETE',
body: { imageId },
});
} catch (photoDeleteError: unknown) {
const errorMessage = getErrorMessage(photoDeleteError);
logger.error('Photo deletion failed', {
action: 'delete_submission_photo',
imageId,
error: errorMessage
});
}
});
const deleteResults = await invokeBatchWithTracking(
validImageIds.map(imageId => ({
functionName: 'upload-image',
payload: { action: 'delete', imageId },
})),
undefined
);
await Promise.allSettled(deletePromises);
deletedPhotoCount = validImageIds.length;
// Count successful deletions
const successfulDeletions = deleteResults.filter(r => !r.error);
deletedPhotoCount = successfulDeletions.length;
// Log any failures
const failedDeletions = deleteResults.filter(r => r.error);
if (failedDeletions.length > 0) {
logger.error('Some photo deletions failed', {
action: 'delete_submission_photos',
failureCount: failedDeletions.length,
totalAttempted: validImageIds.length,
failedRequestIds: failedDeletions.map(r => r.requestId),
});
}
}
}
}

View File

@@ -0,0 +1,108 @@
/**
* Moderation Lock Monitor
*
* Monitors lock expiry and provides automatic renewal prompts for moderators.
* Prevents loss of work due to expired locks.
*/
import { useEffect } from 'react';
import type { ModerationState } from '../moderationStateMachine';
import type { ModerationAction } from '../moderationStateMachine';
import { hasActiveLock, needsLockRenewal } from '../moderationStateMachine';
import { toast } from '@/hooks/use-toast';
import { supabase } from '@/integrations/supabase/client';
import { logger } from '../logger';
/**
* Hook to monitor lock status and warn about expiry
*
* @param state - Current moderation state
* @param dispatch - State machine dispatch function
* @param itemId - ID of the locked item (optional, for manual extension)
*/
export function useLockMonitor(
state: ModerationState,
dispatch: React.Dispatch<ModerationAction>,
itemId?: string
) {
useEffect(() => {
if (!hasActiveLock(state)) {
return;
}
const checkInterval = setInterval(() => {
if (needsLockRenewal(state)) {
logger.warn('Lock expiring soon', {
action: 'lock_expiry_warning',
itemId,
lockExpires: state.status === 'locked' || state.status === 'reviewing'
? state.lockExpires
: undefined,
});
// Dispatch lock expiry warning
dispatch({ type: 'LOCK_EXPIRED' });
// Show toast with extension option
toast({
title: 'Lock Expiring Soon',
description: 'Your lock on this submission will expire in less than 2 minutes. Click to extend.',
variant: 'default',
});
}
}, 30000); // Check every 30 seconds
return () => clearInterval(checkInterval);
}, [state, dispatch, itemId]);
}
/**
* Extend the lock on a submission
*
* @param submissionId - Submission ID
* @param dispatch - State machine dispatch function
*/
async function handleExtendLock(
submissionId: string,
dispatch: React.Dispatch<ModerationAction>
) {
try {
// Call Supabase to extend lock (assumes 15 minute extension)
const { error } = await supabase
.from('content_submissions')
.update({
locked_until: new Date(Date.now() + 15 * 60 * 1000).toISOString(),
})
.eq('id', submissionId);
if (error) throw error;
// Update state machine with new lock time
dispatch({
type: 'LOCK_ACQUIRED',
payload: { lockExpires: new Date(Date.now() + 15 * 60 * 1000).toISOString() },
});
toast({
title: 'Lock Extended',
description: 'You have 15 more minutes to complete your review.',
});
logger.info('Lock extended successfully', {
action: 'lock_extended',
submissionId,
});
} catch (error) {
logger.error('Failed to extend lock', {
action: 'extend_lock_error',
submissionId,
error: error instanceof Error ? error.message : String(error),
});
toast({
title: 'Extension Failed',
description: 'Could not extend lock. Please save your work and re-claim the item.',
variant: 'destructive',
});
}
}