151 lines
5.5 KiB
TypeScript
151 lines
5.5 KiB
TypeScript
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]],
|
|
];
|
|
|
|
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 <T>(
|
|
key: string,
|
|
fetcher: () => Promise<T>,
|
|
timeout: number = 25000,
|
|
storage: CacheStorageType = 'local'
|
|
): Promise<T> => {
|
|
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 || 'http://localhost:3333';
|
|
|
|
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 || 'http://localhost:3333';
|
|
|
|
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();
|
|
};
|
|
|
|
|
|
|