mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-23 22:11:24 -05:00
feat: Implement comprehensive request tracking and state management
This commit is contained in:
@@ -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),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
108
src/lib/moderation/lockMonitor.ts
Normal file
108
src/lib/moderation/lockMonitor.ts
Normal 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',
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user