mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-24 06:51:13 -05:00
Apply all API enhancements
This commit is contained in:
@@ -1,22 +1,54 @@
|
||||
/**
|
||||
* Profile Activity Hook
|
||||
*
|
||||
* Fetches user activity feed with privacy checks and batch optimization.
|
||||
* Eliminates N+1 queries for photo submissions.
|
||||
* Fetches user activity feed with privacy checks and optimized batch fetching.
|
||||
* Prevents N+1 queries by batch fetching photo submission entities.
|
||||
*
|
||||
* Features:
|
||||
* - Privacy-aware filtering based on user preferences
|
||||
* - Batch fetches related entities (parks, rides) for photo submissions
|
||||
* - Combines reviews, credits, submissions, and rankings
|
||||
* - Returns top 15 most recent activities
|
||||
* - 3 minute cache for frequently updated data
|
||||
* - Performance monitoring in dev mode
|
||||
*
|
||||
* @param userId - UUID of the profile user
|
||||
* @param isOwnProfile - Whether viewing user is the profile owner
|
||||
* @param isModerator - Whether viewing user is a moderator
|
||||
* @returns Combined activity feed sorted by date
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const { data: activity } = useProfileActivity(userId, isOwnProfile, isModerator());
|
||||
*
|
||||
* activity?.forEach(item => {
|
||||
* if (item.type === 'review') console.log('Review:', item.rating);
|
||||
* if (item.type === 'submission') console.log('Submission:', item.submission_type);
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useQuery, UseQueryResult } from '@tanstack/react-query';
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { queryKeys } from '@/lib/queryKeys';
|
||||
|
||||
// Type-safe activity item types
|
||||
type ActivityItem =
|
||||
| { type: 'review'; [key: string]: any }
|
||||
| { type: 'credit'; [key: string]: any }
|
||||
| { type: 'submission'; [key: string]: any }
|
||||
| { type: 'ranking'; [key: string]: any };
|
||||
|
||||
export function useProfileActivity(
|
||||
userId: string | undefined,
|
||||
isOwnProfile: boolean,
|
||||
isModerator: boolean
|
||||
) {
|
||||
): UseQueryResult<ActivityItem[]> {
|
||||
return useQuery({
|
||||
queryKey: queryKeys.profile.activity(userId || '', isOwnProfile, isModerator),
|
||||
queryFn: async () => {
|
||||
const startTime = performance.now();
|
||||
|
||||
if (!userId) return [];
|
||||
|
||||
// Check privacy settings first
|
||||
@@ -98,18 +130,45 @@ export function useProfileActivity(
|
||||
rideIds.length ? supabase.from('rides').select('id, name, slug, parks!inner(name, slug)').in('id', rideIds).then(r => r.data || []) : []
|
||||
]);
|
||||
|
||||
// Create lookup maps
|
||||
const photoSubMap = new Map(photoSubs.map(ps => [ps.submission_id, ps]));
|
||||
const photoItemsMap = new Map<string, any[]>();
|
||||
photoItems?.forEach(item => {
|
||||
// Create lookup maps with proper typing
|
||||
interface PhotoSubmissionData {
|
||||
id: string;
|
||||
submission_id: string;
|
||||
entity_type: string;
|
||||
entity_id: string;
|
||||
}
|
||||
|
||||
interface PhotoItem {
|
||||
photo_submission_id: string;
|
||||
cloudflare_image_url: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
interface EntityData {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
parks?: {
|
||||
name: string;
|
||||
slug: string;
|
||||
};
|
||||
}
|
||||
|
||||
const photoSubMap = new Map<string, PhotoSubmissionData>(
|
||||
photoSubs.map(ps => [ps.submission_id, ps as PhotoSubmissionData])
|
||||
);
|
||||
|
||||
const photoItemsMap = new Map<string, PhotoItem[]>();
|
||||
photoItems?.forEach((item: PhotoItem) => {
|
||||
if (!photoItemsMap.has(item.photo_submission_id)) {
|
||||
photoItemsMap.set(item.photo_submission_id, []);
|
||||
}
|
||||
photoItemsMap.get(item.photo_submission_id)!.push(item);
|
||||
});
|
||||
const entityMap = new Map<string, any>([
|
||||
...parks.map((p: any) => [p.id, p] as [string, any]),
|
||||
...rides.map((r: any) => [r.id, r] as [string, any])
|
||||
|
||||
const entityMap = new Map<string, EntityData>([
|
||||
...parks.map((p: any): [string, EntityData] => [p.id, p]),
|
||||
...rides.map((r: any): [string, EntityData] => [r.id, r])
|
||||
]);
|
||||
|
||||
// Enrich submissions
|
||||
@@ -137,7 +196,7 @@ export function useProfileActivity(
|
||||
}
|
||||
|
||||
// Combine and sort
|
||||
const combined = [
|
||||
const combined: ActivityItem[] = [
|
||||
...reviews.map(r => ({ ...r, type: 'review' as const })),
|
||||
...credits.map(c => ({ ...c, type: 'credit' as const })),
|
||||
...submissions.map(s => ({ ...s, type: 'submission' as const })),
|
||||
@@ -145,6 +204,19 @@ export function useProfileActivity(
|
||||
].sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime())
|
||||
.slice(0, 15);
|
||||
|
||||
// Performance monitoring (dev only)
|
||||
if (import.meta.env.DEV) {
|
||||
const duration = performance.now() - startTime;
|
||||
if (duration > 1500) {
|
||||
console.warn(`⚠️ Slow query: useProfileActivity took ${duration.toFixed(0)}ms`, {
|
||||
userId,
|
||||
itemCount: combined.length,
|
||||
reviewCount: reviews.length,
|
||||
submissionCount: submissions.length
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return combined;
|
||||
},
|
||||
enabled: !!userId,
|
||||
|
||||
Reference in New Issue
Block a user