Refactor: Implement Phase 3C cleanup

This commit is contained in:
gpt-engineer-app[bot]
2025-10-03 14:24:43 +00:00
parent b8f1efee97
commit b778fc95f6
6 changed files with 119 additions and 81 deletions

View File

@@ -78,7 +78,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
const fetchItems = async (entityFilter: EntityFilter = 'all', statusFilter: StatusFilter = 'pending') => {
if (!user) {
console.log('Skipping fetch - user not authenticated');
return;
}
@@ -327,9 +326,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
// Sort by creation date (newest first for better UX)
formattedItems.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
console.log('Formatted items:', formattedItems);
console.log('Photo submissions:', formattedItems.filter(item => item.submission_type === 'photo'));
setItems(formattedItems);
} catch (error: any) {
@@ -352,7 +348,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
// Set up realtime subscriptions
const { connectionState: submissionsConnectionState, reconnect: reconnectSubmissions } = useRealtimeSubmissions({
onInsert: (payload) => {
console.log('New submission received');
toast({
title: 'New Submission',
description: 'A new content submission has been added',
@@ -360,7 +355,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
fetchItems(activeEntityFilter, activeStatusFilter);
},
onUpdate: (payload) => {
console.log('Submission updated');
// Update items state directly for better UX
setItems(prevItems =>
prevItems.map(item =>
@@ -371,7 +365,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
);
},
onDelete: (payload) => {
console.log('Submission deleted');
setItems(prevItems =>
prevItems.filter(item => !(item.id === payload.old.id && item.type === 'content_submission'))
);
@@ -469,7 +462,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
) => {
// Prevent multiple clicks on the same item
if (actionLoading === item.id) {
console.log('Action already in progress for item:', item.id);
return;
}
@@ -585,8 +577,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
// Handle photo submissions - create photos records when approved
if (action === 'approved' && item.type === 'content_submission' && item.submission_type === 'photo') {
console.log('🖼️ [PHOTO APPROVAL] Starting photo submission approval');
try {
// Fetch photo submission from new relational tables
const { data: photoSubmission, error: fetchError } = await supabase
@@ -599,15 +589,13 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
.eq('submission_id', item.id)
.single();
console.log('🖼️ [PHOTO APPROVAL] Fetched photo submission:', photoSubmission);
if (fetchError || !photoSubmission) {
console.error('🖼️ [PHOTO APPROVAL] ERROR: Failed to fetch photo submission:', fetchError);
console.error('Failed to fetch photo submission:', fetchError);
throw new Error('Failed to fetch photo submission data');
}
if (!photoSubmission.items || photoSubmission.items.length === 0) {
console.error('🖼️ [PHOTO APPROVAL] ERROR: No photo items found');
console.error('No photo items found in submission');
throw new Error('No photos found in submission');
}
@@ -617,10 +605,7 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
.select('id')
.eq('submission_id', item.id);
console.log('🖼️ [PHOTO APPROVAL] Existing photos check:', existingPhotos);
if (existingPhotos && existingPhotos.length > 0) {
console.log('🖼️ [PHOTO APPROVAL] Photos already exist for this submission, skipping creation');
// Just update submission status
const { error: updateError } = await supabase
@@ -650,19 +635,15 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
approved_at: new Date().toISOString(),
}));
console.log('🖼️ [PHOTO APPROVAL] Creating photo records:', photoRecords);
const { data: createdPhotos, error: insertError } = await supabase
.from('photos')
.insert(photoRecords)
.select();
if (insertError) {
console.error('🖼️ [PHOTO APPROVAL] ERROR: Failed to insert photos:', insertError);
console.error('Failed to insert photos:', insertError);
throw insertError;
}
console.log('🖼️ [PHOTO APPROVAL] ✅ Successfully created photos:', createdPhotos);
}
// Update submission status
@@ -677,12 +658,10 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
.eq('id', item.id);
if (updateError) {
console.error('🖼️ [PHOTO APPROVAL] Error updating submission:', updateError);
console.error('Error updating submission:', updateError);
throw updateError;
}
console.log('🖼️ [PHOTO APPROVAL] ✅ Complete! Photos approved and published');
toast({
title: "Photos Approved",
description: `Successfully approved and published ${photoSubmission.items.length} photo(s)`,
@@ -693,8 +672,7 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
return;
} catch (error: any) {
console.error('🖼️ [PHOTO APPROVAL] ❌ FATAL ERROR:', error);
console.error('🖼️ [PHOTO APPROVAL] Error details:', error.message, error.code, error.details);
console.error('Photo approval error:', error);
throw error;
}
}
@@ -708,8 +686,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
.in('status', ['pending', 'rejected']);
if (!itemsError && submissionItems && submissionItems.length > 0) {
console.log(`Found ${submissionItems.length} pending submission items for ${item.id}`);
if (action === 'approved') {
// Call the edge function to process all items
const { data: approvalData, error: approvalError } = await supabase.functions.invoke(
@@ -727,8 +703,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
throw new Error(`Failed to process submission items: ${approvalError.message}`);
}
console.log('Submission items processed successfully:', approvalData);
toast({
title: "Submission Approved",
description: `Successfully processed ${submissionItems.length} item(s)`,
@@ -739,7 +713,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
return;
} else if (action === 'rejected') {
// Cascade rejection to all pending items
console.log('Cascading rejection to submission items');
const { error: rejectError } = await supabase
.from('submission_items')
.update({
@@ -753,8 +726,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
if (rejectError) {
console.error('Failed to cascade rejection:', rejectError);
// Don't fail the whole operation, just log it
} else {
console.log('Successfully cascaded rejection to submission items');
}
}
}
@@ -782,8 +753,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
updateData.reviewer_notes = moderatorNotes;
}
console.log('Updating item:', item.id, 'with data:', updateData, 'table:', table);
const { error, data } = await supabase
.from(table)
.update(updateData)
@@ -795,16 +764,12 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
throw error;
}
console.log('Update response:', { data, rowsAffected: data?.length });
// Check if the update actually affected any rows
if (!data || data.length === 0) {
console.error('No rows were updated. This might be due to RLS policies or the item not existing.');
throw new Error('Failed to update item - no rows affected. You might not have permission to moderate this content.');
}
console.log('Update successful, rows affected:', data.length);
toast({
title: `Content ${action}`,
description: `The ${item.type} has been ${action}`,
@@ -824,10 +789,10 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
return newNotes;
});
// Only refresh if we're viewing a filter that should no longer show this item
// Refresh if needed based on filter
if ((activeStatusFilter === 'pending' && (action === 'approved' || action === 'rejected')) ||
(activeStatusFilter === 'flagged' && (action === 'approved' || action === 'rejected'))) {
console.log('Item no longer matches filter, removing from view');
// Item no longer matches filter
}
} catch (error: any) {
@@ -855,7 +820,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
// Prevent duplicate calls
if (actionLoading === item.id) {
console.log('Deletion already in progress for:', item.id);
return;
}
@@ -865,8 +829,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
setItems(prev => prev.filter(i => i.id !== item.id));
try {
console.log('Starting deletion process for submission:', item.id);
// Step 1: Extract photo IDs from the submission content
const photoIds: string[] = [];
const validImageIds: string[] = [];
@@ -876,18 +838,12 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
const photosArray = item.content?.content?.photos || item.content?.photos;
if (photosArray && Array.isArray(photosArray)) {
console.log('Processing photos from content:', photosArray);
for (const photo of photosArray) {
console.log('Processing photo object:', photo);
console.log('Photo keys:', Object.keys(photo));
console.log('photo.imageId:', photo.imageId, 'type:', typeof photo.imageId);
let imageId = '';
// First try to use the stored imageId directly
if (photo.imageId) {
imageId = photo.imageId;
console.log('Using stored image ID:', imageId);
} else if (photo.url) {
// Check if this looks like a Cloudflare image ID (not a blob URL)
if (photo.url.startsWith('blob:')) {
@@ -909,7 +865,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
imageId = cloudflareMatch[1];
}
}
console.log('Extracted image ID from URL:', imageId, 'from URL:', photo.url);
}
if (imageId) {
@@ -922,14 +877,10 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
}
}
console.log(`Found ${validImageIds.length} valid image IDs to delete, ${skippedPhotos.length} photos will be orphaned`);
// Step 2: Delete photos from Cloudflare Images (if any valid IDs)
if (validImageIds.length > 0) {
const deletePromises = validImageIds.map(async (imageId) => {
try {
console.log('Attempting to delete image from Cloudflare:', imageId);
// Use Supabase SDK - automatically includes session token
const { data, error } = await supabase.functions.invoke('upload-image', {
method: 'DELETE',
@@ -940,8 +891,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
throw new Error(`Failed to delete image: ${error.message}`);
}
console.log('Successfully deleted image:', imageId, data);
} catch (deleteError) {
console.error(`Failed to delete photo ${imageId} from Cloudflare:`, deleteError);
// Continue with other deletions - don't fail the entire operation
@@ -953,7 +902,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
}
// Step 3: Delete the submission from the database
console.log('Deleting submission from database:', item.id);
const { error } = await supabase
.from('content_submissions')
.delete()
@@ -974,8 +922,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
if (checkData && !checkError) {
console.error('DELETION FAILED: Item still exists in database after delete operation');
throw new Error('Deletion failed - item still exists in database');
} else {
console.log('Verified: Submission successfully deleted from database');
}
const deletedCount = validImageIds.length;
@@ -1202,7 +1148,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
console.error('Failed to load review photo:', photo.url);
(e.target as HTMLImageElement).style.display = 'none';
}}
onLoad={() => console.log('Review photo loaded:', photo.url)}
/>
<div className="absolute inset-0 flex items-center justify-center bg-black/50 text-white text-xs opacity-0 hover:opacity-100 transition-opacity rounded">
<Eye className="w-4 h-4" />
@@ -1269,7 +1214,6 @@ export const ModerationQueue = forwardRef<ModerationQueueRef>((props, ref) => {
`;
}
}}
onLoad={() => console.log('Photo submission loaded:', photo.url)}
/>
<div className="absolute inset-0 flex items-center justify-center bg-black/50 text-white opacity-0 hover:opacity-100 transition-opacity rounded">
<Eye className="w-5 h-5" />