Apply all API enhancements

This commit is contained in:
gpt-engineer-app[bot]
2025-10-30 23:55:18 +00:00
parent 8f4110d890
commit d40f0f13aa
10 changed files with 435 additions and 45 deletions

View File

@@ -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,