import { queryClient } from './queryClient'; import { supabase as defaultSupabase } from "@/integrations/supabase/client"; import { SupabaseClient } from "@supabase/supabase-js"; // Deprecated: Caching now handled by React Query // Keeping for backward compatibility type CacheStorageType = 'memory' | 'local'; /** * Parse a flat string key into a hierarchical query key array. * This enables SSE cascade invalidation via StreamInvalidator: * invalidateQueries({ queryKey: ['posts'] }) matches ['posts', 'abc123'] */ const KEY_PREFIXES: [RegExp, (...groups: string[]) => string[]][] = [ [/^full-post-(.+)$/, (_, id) => ['posts', 'full', id]], [/^post-(.+)$/, (_, id) => ['posts', id]], [/^like-(.+)-(.+)$/, (_, uid, pid) => ['likes', uid, pid]], [/^selected-versions-(.+)$/, (_, ids) => ['pictures', 'selected-versions', ids]], [/^versions-(.+)$/, (_, rest) => ['pictures', 'versions', rest]], [/^picture-(.+)$/, (_, id) => ['pictures', id]], [/^pictures-(.+)$/, (_, rest) => ['pictures', rest]], [/^user-page-(.+?)-(.+)$/, (_, uid, slug) => ['pages', 'user-page', uid, slug]], [/^user-pages-(.+)$/, (_, uid) => ['pages', 'user-pages', uid]], [/^page-details-(.+)$/, (_, id) => ['pages', 'details', id]], [/^page-(.+)$/, (_, id) => ['pages', id]], [/^pages-(.+)$/, (_, rest) => ['pages', rest]], [/^profile-(.+)$/, (_, id) => ['users', id]], [/^settings-(.+)$/, (_, id) => ['users', 'settings', id]], [/^roles-(.+)$/, (_, id) => ['users', 'roles', id]], [/^openai-(.+)$/, (_, id) => ['users', 'openai', id]], [/^api-keys-(.+)$/, (_, id) => ['users', 'api-keys', id]], [/^google-(.+)$/, (_, id) => ['users', 'google', id]], [/^user-secrets-(.+)$/, (_, id) => ['users', 'secrets', id]], [/^provider-(.+)-(.+)$/, (_, uid, prov) => ['users', 'provider', uid, prov]], [/^type-(.+)$/, (_, id) => ['types', id]], [/^types-(.+)$/, (_, rest) => ['types', rest]], [/^i18n-(.+)$/, (_, rest) => ['i18n', rest]], [/^acl-(.+?)-(.+)$/, (_, type, id) => ['acl', type, id]], [/^layout-(.+)$/, (_, id) => ['layouts', id]], [/^layouts-(.+)$/, (_, rest) => ['layouts', rest]], ]; export const parseQueryKey = (key: string): string[] => { for (const [pattern, builder] of KEY_PREFIXES) { const match = key.match(pattern); if (match) return builder(...match); } return [key]; // fallback: wrap as single-element array }; export const fetchWithDeduplication = async ( key: string, fetcher: () => Promise, timeout: number = 25000, storage: CacheStorageType = 'local' ): Promise => { const queryKey = parseQueryKey(key); return queryClient.fetchQuery({ queryKey, queryFn: fetcher, staleTime: timeout > 1 ? timeout : 1000 * 30, }); }; export const invalidateCache = (key: string) => { const queryKey = parseQueryKey(key); queryClient.invalidateQueries({ queryKey }); }; export const invalidateServerCache = async (types: string[]) => { // Explicit cache invalidation is handled by the server on mutation // No need to call /api/cache/invalidate manually console.debug('invalidateServerCache: Skipped manual invalidation for', types); }; export const checkLikeStatus = async (userId: string, pictureId: string, client?: SupabaseClient) => { const supabase = client || defaultSupabase; return fetchWithDeduplication(`like-${userId}-${pictureId}`, async () => { const { data, error } = await supabase .from('likes') .select('id') .eq('user_id', userId) .eq('picture_id', pictureId) .maybeSingle(); if (error) throw error; return !!data; }); }; export const fetchAnalytics = async (options: { limit?: number, startDate?: string, endDate?: string } = {}) => { const { data: sessionData } = await defaultSupabase.auth.getSession(); const token = sessionData.session?.access_token; if (!token) throw new Error('No active session'); const headers: HeadersInit = {}; headers['Authorization'] = `Bearer ${token}`; const params = new URLSearchParams(); if (options.limit) params.append('limit', String(options.limit)); if (options.startDate) params.append('startDate', options.startDate); if (options.endDate) params.append('endDate', options.endDate); // Server URL logic from fetchMediaItemsByIds const serverUrl = import.meta.env.VITE_SERVER_IMAGE_API_URL; const res = await fetch(`${serverUrl}/api/analytics?${params.toString()}`, { headers }); if (!res.ok) { if (res.status === 403 || res.status === 401) { throw new Error('Unauthorized'); } throw new Error(`Failed to fetch analytics: ${res.statusText}`); } return await res.json(); }; export const clearAnalytics = async () => { const { data: sessionData } = await defaultSupabase.auth.getSession(); const token = sessionData.session?.access_token; if (!token) throw new Error('No active session'); const headers: HeadersInit = {}; headers['Authorization'] = `Bearer ${token}`; // Server URL logic from fetchMediaItemsByIds const serverUrl = import.meta.env.VITE_SERVER_IMAGE_API_URL; const res = await fetch(`${serverUrl}/api/analytics`, { method: 'DELETE', headers }); if (!res.ok) { if (res.status === 403 || res.status === 401) { throw new Error('Unauthorized'); } throw new Error(`Failed to clear analytics: ${res.statusText}`); } return await res.json(); };