Reverted to commit 0091584677

This commit is contained in:
gpt-engineer-app[bot]
2025-11-01 15:22:30 +00:00
parent 26e5753807
commit 133141d474
125 changed files with 2316 additions and 9102 deletions

View File

@@ -7,7 +7,6 @@ import { validateMultipleItems } from '@/lib/entityValidationSchemas';
import { invokeWithTracking } from '@/lib/edgeFunctionTracking';
import type { User } from '@supabase/supabase-js';
import type { ModerationItem } from '@/types/moderation';
import { useQueryInvalidation } from '@/lib/queryInvalidation';
/**
* Configuration for moderation actions
@@ -30,42 +29,15 @@ export interface ModerationActions {
}
/**
* Moderation Actions Hook
* Hook for moderation action handlers
* Extracted from useModerationQueueManager for better separation of concerns
*
* Provides functions for performing moderation actions on content submissions.
* Handles approval, rejection, deletion, and retry operations with proper
* cache invalidation and audit logging.
*
* Features:
* - Photo submission processing
* - Submission item validation
* - Selective approval via edge function
* - Comprehensive error handling
* - Cache invalidation for affected entities
* - Audit trail logging
* - Performance monitoring
*
* @param config - Configuration with user, callbacks, and lock state
* @param config - Configuration object with user, callbacks, and dependencies
* @returns Object with action handler functions
*
* @example
* ```tsx
* const actions = useModerationActions({
* user,
* onActionStart: (id) => console.log('Starting:', id),
* onActionComplete: () => refetch(),
* currentLockSubmissionId: lockedId
* });
*
* await actions.performAction(item, 'approved', 'Looks good!');
* ```
*/
export function useModerationActions(config: ModerationActionsConfig): ModerationActions {
const { user, onActionStart, onActionComplete } = config;
const { toast } = useToast();
// Cache invalidation for moderation and affected entities
const invalidation = useQueryInvalidation();
/**
* Perform moderation action (approve/reject)
@@ -291,30 +263,6 @@ export function useModerationActions(config: ModerationActionsConfig): Moderatio
description: `The ${item.type} has been ${action}`,
});
// Invalidate specific entity caches based on submission type
if (action === 'approved') {
if (item.submission_type === 'photo' && item.content) {
const entityType = item.content.entity_type as string;
const entityId = item.content.entity_id as string;
if (entityType && entityId) {
invalidation.invalidateEntityPhotos(entityType, entityId);
invalidation.invalidatePhotoCount(entityType, entityId);
}
} else if (item.submission_type === 'park') {
invalidation.invalidateParks();
invalidation.invalidateHomepageData('parks');
} else if (item.submission_type === 'ride') {
invalidation.invalidateRides();
invalidation.invalidateHomepageData('rides');
} else if (item.submission_type === 'company') {
invalidation.invalidateHomepageData('all');
}
}
// Always invalidate moderation queue
invalidation.invalidateModerationQueue();
invalidation.invalidateModerationStats();
logger.log(`✅ Action ${action} completed for ${item.id}`);
} catch (error: unknown) {
logger.error('❌ Error performing action:', { error: getErrorMessage(error) });

View File

@@ -1,80 +0,0 @@
import { useQuery } from '@tanstack/react-query';
import { supabase } from '@/integrations/supabase/client';
import { queryKeys } from '@/lib/queryKeys';
import type { PhotoItem } from '@/types/photos';
/**
* usePhotoSubmission Hook
*
* Fetches photo submission with items using optimized JOIN query.
*
* Features:
* - Single query with JOIN instead of 2 sequential queries (75% reduction)
* - Caches for 3 minutes (moderation content, moderate volatility)
* - Transforms to PhotoItem[] for PhotoGrid compatibility
* - Performance monitoring with slow query warnings
*
* @param submissionId - Content submission UUID
*
* @returns TanStack Query result with PhotoItem array
*
* @example
* ```tsx
* const { data: photos, isLoading, error } = usePhotoSubmission(submissionId);
*
* if (photos && photos.length > 0) {
* return <PhotoGrid photos={photos} />;
* }
* ```
*/
export function usePhotoSubmission(submissionId?: string) {
return useQuery<PhotoItem[]>({
queryKey: queryKeys.moderation.photoSubmission(submissionId),
queryFn: async () => {
if (!submissionId) return [];
const startTime = performance.now();
// Step 1: Get photo_submission_id from submission_id
const { data: photoSubmission, error: photoSubmissionError } = await supabase
.from('photo_submissions')
.select('id, entity_type, title')
.eq('submission_id', submissionId)
.maybeSingle();
if (photoSubmissionError) throw photoSubmissionError;
if (!photoSubmission) return [];
// Step 2: Get photo items using photo_submission_id
const { data: items, error: itemsError } = await supabase
.from('photo_submission_items')
.select('*')
.eq('photo_submission_id', photoSubmission.id)
.order('order_index');
if (itemsError) throw itemsError;
const duration = performance.now() - startTime;
// Log slow queries in development
if (import.meta.env.DEV && duration > 1000) {
console.warn(`Slow query: usePhotoSubmission took ${duration}ms`, { submissionId });
}
// Transform to PhotoItem[] for PhotoGrid compatibility
return (items || []).map((item) => ({
id: item.id,
url: item.cloudflare_image_url,
filename: item.filename || `Photo ${item.order_index + 1}`,
caption: item.caption,
title: item.title,
date_taken: item.date_taken,
}));
},
enabled: !!submissionId,
staleTime: 3 * 60 * 1000, // 3 minutes
gcTime: 10 * 60 * 1000, // 10 minutes
refetchOnWindowFocus: false,
});
}

View File

@@ -1,154 +0,0 @@
import { useQuery } from '@tanstack/react-query';
import { supabase } from '@/integrations/supabase/client';
import { queryKeys } from '@/lib/queryKeys';
import { useAuth } from '@/hooks/useAuth';
/**
* useRecentActivity Hook
*
* Fetches recent moderation activity across all types for activity feed.
*
* Features:
* - 3 parallel queries (submissions, reports, reviews) + 1 batch profile fetch
* - Caches for 2 minutes (activity feed, should be relatively fresh)
* - Smart merging for background refetches (preserves scroll position)
* - Performance monitoring with slow query warnings
*
* @returns TanStack Query result with activity items array
*
* @example
* ```tsx
* const { data: activities, isLoading, refetch } = useRecentActivity();
*
* // Manual refresh trigger:
* <Button onClick={() => refetch()}>Refresh Activity</Button>
* ```
*/
interface ActivityItem {
id: string;
type: 'submission' | 'report' | 'review';
action: 'approved' | 'rejected' | 'reviewed' | 'dismissed' | 'flagged';
entity_type?: string;
entity_name?: string;
timestamp: string;
moderator_id?: string;
moderator?: {
username: string;
display_name?: string;
avatar_url?: string;
};
}
export function useRecentActivity() {
const { user } = useAuth();
return useQuery<ActivityItem[]>({
queryKey: queryKeys.moderation.recentActivity,
queryFn: async () => {
const startTime = performance.now();
// Fetch all activity types in parallel
const [submissionsResult, reportsResult, reviewsResult] = await Promise.all([
supabase
.from('content_submissions')
.select('id, submission_type, status, updated_at, reviewer_id')
.in('status', ['approved', 'rejected'])
.order('updated_at', { ascending: false })
.limit(10),
supabase
.from('reports')
.select('id, reported_entity_type, status, updated_at, reviewed_by')
.in('status', ['resolved', 'dismissed'])
.order('updated_at', { ascending: false })
.limit(10),
supabase
.from('reviews')
.select('id, ride_id, park_id, moderation_status, moderated_at, moderated_by')
.eq('moderation_status', 'flagged')
.not('moderated_at', 'is', null)
.order('moderated_at', { ascending: false })
.limit(10),
]);
// Check for errors
if (submissionsResult.error) throw submissionsResult.error;
if (reportsResult.error) throw reportsResult.error;
if (reviewsResult.error) throw reviewsResult.error;
const submissions = submissionsResult.data || [];
const reports = reportsResult.data || [];
const reviews = reviewsResult.data || [];
// Collect all unique moderator IDs
const moderatorIds = new Set<string>();
submissions.forEach((s) => s.reviewer_id && moderatorIds.add(s.reviewer_id));
reports.forEach((r) => r.reviewed_by && moderatorIds.add(r.reviewed_by));
reviews.forEach((r) => r.moderated_by && moderatorIds.add(r.moderated_by));
// Batch fetch moderator profiles
let moderatorMap = new Map<string, any>();
if (moderatorIds.size > 0) {
const { data: profiles, error: profilesError } = await supabase
.from('profiles')
.select('user_id, username, display_name, avatar_url')
.in('user_id', Array.from(moderatorIds));
if (profilesError) throw profilesError;
moderatorMap = new Map(
(profiles || []).map((p) => [p.user_id, p])
);
}
// Transform to ActivityItem[]
const activities: ActivityItem[] = [
...submissions.map((s) => ({
id: s.id,
type: 'submission' as const,
action: s.status as 'approved' | 'rejected',
entity_type: s.submission_type,
timestamp: s.updated_at,
moderator_id: s.reviewer_id || undefined,
moderator: s.reviewer_id ? moderatorMap.get(s.reviewer_id) : undefined,
})),
...reports.map((r) => ({
id: r.id,
type: 'report' as const,
action: (r.status === 'resolved' ? 'reviewed' : 'dismissed') as 'reviewed' | 'dismissed',
entity_type: r.reported_entity_type,
timestamp: r.updated_at,
moderator_id: r.reviewed_by || undefined,
moderator: r.reviewed_by ? moderatorMap.get(r.reviewed_by) : undefined,
})),
...reviews.map((r) => ({
id: r.id,
type: 'review' as const,
action: 'flagged' as const,
entity_type: r.ride_id ? 'ride' : 'park',
timestamp: r.moderated_at!,
moderator_id: r.moderated_by || undefined,
moderator: r.moderated_by ? moderatorMap.get(r.moderated_by) : undefined,
})),
];
// Sort by timestamp descending and limit to 20
activities.sort((a, b) =>
new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
);
const duration = performance.now() - startTime;
// Log slow queries in development
if (import.meta.env.DEV && duration > 1000) {
console.warn(`Slow query: useRecentActivity took ${duration}ms`);
}
return activities.slice(0, 20);
},
enabled: !!user,
staleTime: 2 * 60 * 1000, // 2 minutes
gcTime: 5 * 60 * 1000, // 5 minutes
refetchOnWindowFocus: false,
});
}