mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-24 11:31:12 -05:00
Reverted to commit 0091584677
This commit is contained in:
@@ -1,32 +0,0 @@
|
||||
/**
|
||||
* Auth0 Configuration
|
||||
*
|
||||
* Centralized configuration for Auth0 authentication
|
||||
*/
|
||||
|
||||
export const auth0Config = {
|
||||
domain: import.meta.env.VITE_AUTH0_DOMAIN || '',
|
||||
clientId: import.meta.env.VITE_AUTH0_CLIENT_ID || '',
|
||||
authorizationParams: {
|
||||
redirect_uri: typeof window !== 'undefined' ? `${window.location.origin}/auth/callback` : '',
|
||||
audience: import.meta.env.VITE_AUTH0_DOMAIN ? `https://${import.meta.env.VITE_AUTH0_DOMAIN}/api/v2/` : '',
|
||||
scope: 'openid profile email'
|
||||
},
|
||||
cacheLocation: 'localstorage' as const,
|
||||
useRefreshTokens: true,
|
||||
useRefreshTokensFallback: true,
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if Auth0 is properly configured
|
||||
*/
|
||||
export function isAuth0Configured(): boolean {
|
||||
return !!(auth0Config.domain && auth0Config.clientId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Auth0 Management API audience
|
||||
*/
|
||||
export function getManagementAudience(): string {
|
||||
return `https://${auth0Config.domain}/api/v2/`;
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
/**
|
||||
* Auth0 Management API Helper
|
||||
*
|
||||
* Provides helper functions to interact with Auth0 Management API
|
||||
*/
|
||||
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import type { Auth0MFAStatus, Auth0RoleInfo, ManagementTokenResponse } from '@/types/auth0';
|
||||
|
||||
/**
|
||||
* Get Auth0 Management API access token via edge function
|
||||
*/
|
||||
export async function getManagementToken(): Promise<string> {
|
||||
const { data, error } = await supabase.functions.invoke<ManagementTokenResponse>(
|
||||
'auth0-get-management-token',
|
||||
{ method: 'POST' }
|
||||
);
|
||||
|
||||
if (error || !data) {
|
||||
throw new Error('Failed to get management token: ' + (error?.message || 'Unknown error'));
|
||||
}
|
||||
|
||||
return data.access_token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user's MFA enrollment status
|
||||
*/
|
||||
export async function getMFAStatus(userId: string): Promise<Auth0MFAStatus> {
|
||||
try {
|
||||
const token = await getManagementToken();
|
||||
const domain = import.meta.env.VITE_AUTH0_DOMAIN;
|
||||
|
||||
const response = await fetch(`https://${domain}/api/v2/users/${userId}/enrollments`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch MFA status');
|
||||
}
|
||||
|
||||
const enrollments = await response.json();
|
||||
|
||||
return {
|
||||
enrolled: enrollments.length > 0,
|
||||
methods: enrollments.map((e: any) => ({
|
||||
id: e.id,
|
||||
type: e.type,
|
||||
name: e.name,
|
||||
confirmed: e.status === 'confirmed',
|
||||
})),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error fetching MFA status:', error);
|
||||
return { enrolled: false, methods: [] };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user's roles from Auth0
|
||||
*/
|
||||
export async function getUserRoles(userId: string): Promise<Auth0RoleInfo[]> {
|
||||
try {
|
||||
const token = await getManagementToken();
|
||||
const domain = import.meta.env.VITE_AUTH0_DOMAIN;
|
||||
|
||||
const response = await fetch(`https://${domain}/api/v2/users/${userId}/roles`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch user roles');
|
||||
}
|
||||
|
||||
const roles = await response.json();
|
||||
|
||||
return roles.map((role: any) => ({
|
||||
id: role.id,
|
||||
name: role.name,
|
||||
description: role.description,
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('Error fetching user roles:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user metadata
|
||||
*/
|
||||
export async function updateUserMetadata(
|
||||
userId: string,
|
||||
metadata: Record<string, any>
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const token = await getManagementToken();
|
||||
const domain = import.meta.env.VITE_AUTH0_DOMAIN;
|
||||
|
||||
const response = await fetch(`https://${domain}/api/v2/users/${userId}`, {
|
||||
method: 'PATCH',
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
user_metadata: metadata,
|
||||
}),
|
||||
});
|
||||
|
||||
return response.ok;
|
||||
} catch (error) {
|
||||
console.error('Error updating user metadata:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger MFA enrollment for user
|
||||
*/
|
||||
export async function triggerMFAEnrollment(redirectUri?: string): Promise<void> {
|
||||
const domain = import.meta.env.VITE_AUTH0_DOMAIN;
|
||||
const clientId = import.meta.env.VITE_AUTH0_CLIENT_ID;
|
||||
const redirect = redirectUri || `${window.location.origin}/settings`;
|
||||
|
||||
// Redirect to Auth0 MFA enrollment page
|
||||
window.location.href = `https://${domain}/authorize?` +
|
||||
`client_id=${clientId}&` +
|
||||
`response_type=code&` +
|
||||
`redirect_uri=${encodeURIComponent(redirect)}&` +
|
||||
`scope=openid profile email&` +
|
||||
`prompt=enroll`;
|
||||
}
|
||||
@@ -1,387 +0,0 @@
|
||||
/**
|
||||
* Cache Performance Monitoring Utilities
|
||||
*
|
||||
* Provides tools to monitor React Query cache performance in production.
|
||||
* Use sparingly - only enable when debugging performance issues.
|
||||
*
|
||||
* Features:
|
||||
* - Cache hit/miss tracking
|
||||
* - Query duration monitoring
|
||||
* - Slow query detection
|
||||
* - Invalidation frequency tracking
|
||||
*
|
||||
* @example
|
||||
* import { cacheMonitor } from '@/lib/cacheMonitoring';
|
||||
*
|
||||
* // Start monitoring (development only)
|
||||
* if (process.env.NODE_ENV === 'development') {
|
||||
* cacheMonitor.start();
|
||||
* }
|
||||
*
|
||||
* // Get metrics
|
||||
* const metrics = cacheMonitor.getMetrics();
|
||||
* console.log('Cache hit rate:', metrics.hitRate);
|
||||
*/
|
||||
|
||||
import { QueryClient } from '@tanstack/react-query';
|
||||
import { logger } from '@/lib/logger';
|
||||
|
||||
interface CacheMetrics {
|
||||
hits: number;
|
||||
misses: number;
|
||||
hitRate: number;
|
||||
totalQueries: number;
|
||||
avgQueryTime: number;
|
||||
slowQueries: number;
|
||||
invalidations: number;
|
||||
lastReset: Date;
|
||||
}
|
||||
|
||||
interface QueryTiming {
|
||||
queryKey: string;
|
||||
startTime: number;
|
||||
endTime?: number;
|
||||
duration?: number;
|
||||
status: 'pending' | 'success' | 'error';
|
||||
}
|
||||
|
||||
class CacheMonitor {
|
||||
private metrics: CacheMetrics;
|
||||
private queryTimings: Map<string, QueryTiming>;
|
||||
private slowQueryThreshold: number = 500; // ms
|
||||
private enabled: boolean = false;
|
||||
private listeners: {
|
||||
onSlowQuery?: (queryKey: string, duration: number) => void;
|
||||
onCacheMiss?: (queryKey: string) => void;
|
||||
onInvalidation?: (queryKey: string) => void;
|
||||
} = {};
|
||||
|
||||
constructor() {
|
||||
this.metrics = this.resetMetrics();
|
||||
this.queryTimings = new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start monitoring cache performance
|
||||
* Should only be used in development or for debugging
|
||||
*/
|
||||
start(queryClient?: QueryClient) {
|
||||
if (this.enabled) {
|
||||
logger.warn('Cache monitor already started');
|
||||
return;
|
||||
}
|
||||
|
||||
this.enabled = true;
|
||||
this.metrics = this.resetMetrics();
|
||||
|
||||
logger.info('Cache monitor started', {
|
||||
slowQueryThreshold: this.slowQueryThreshold
|
||||
});
|
||||
|
||||
// If queryClient provided, set up automatic tracking
|
||||
if (queryClient) {
|
||||
this.setupQueryClientTracking(queryClient);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop monitoring
|
||||
*/
|
||||
stop() {
|
||||
this.enabled = false;
|
||||
this.queryTimings.clear();
|
||||
logger.info('Cache monitor stopped');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset all metrics
|
||||
*/
|
||||
reset() {
|
||||
this.metrics = this.resetMetrics();
|
||||
this.queryTimings.clear();
|
||||
logger.info('Cache metrics reset');
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a cache hit (data served from cache)
|
||||
*/
|
||||
recordHit(queryKey: string) {
|
||||
if (!this.enabled) return;
|
||||
|
||||
this.metrics.hits++;
|
||||
this.metrics.totalQueries++;
|
||||
this.updateHitRate();
|
||||
|
||||
logger.debug('Cache hit', { queryKey });
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a cache miss (data fetched from server)
|
||||
*/
|
||||
recordMiss(queryKey: string) {
|
||||
if (!this.enabled) return;
|
||||
|
||||
this.metrics.misses++;
|
||||
this.metrics.totalQueries++;
|
||||
this.updateHitRate();
|
||||
|
||||
logger.debug('Cache miss', { queryKey });
|
||||
|
||||
if (this.listeners.onCacheMiss) {
|
||||
this.listeners.onCacheMiss(queryKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start timing a query
|
||||
*/
|
||||
startQuery(queryKey: string) {
|
||||
if (!this.enabled) return;
|
||||
|
||||
const key = this.normalizeQueryKey(queryKey);
|
||||
this.queryTimings.set(key, {
|
||||
queryKey: key,
|
||||
startTime: performance.now(),
|
||||
status: 'pending'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* End timing a query
|
||||
*/
|
||||
endQuery(queryKey: string, status: 'success' | 'error') {
|
||||
if (!this.enabled) return;
|
||||
|
||||
const key = this.normalizeQueryKey(queryKey);
|
||||
const timing = this.queryTimings.get(key);
|
||||
|
||||
if (!timing) {
|
||||
logger.warn('Query timing not found', { queryKey: key });
|
||||
return;
|
||||
}
|
||||
|
||||
const endTime = performance.now();
|
||||
const duration = endTime - timing.startTime;
|
||||
|
||||
timing.endTime = endTime;
|
||||
timing.duration = duration;
|
||||
timing.status = status;
|
||||
|
||||
// Update average query time
|
||||
const totalTime = this.metrics.avgQueryTime * (this.metrics.totalQueries - 1) + duration;
|
||||
this.metrics.avgQueryTime = totalTime / this.metrics.totalQueries;
|
||||
|
||||
// Check for slow query
|
||||
if (duration > this.slowQueryThreshold) {
|
||||
this.metrics.slowQueries++;
|
||||
|
||||
logger.warn('Slow query detected', {
|
||||
queryKey: key,
|
||||
duration: Math.round(duration),
|
||||
threshold: this.slowQueryThreshold
|
||||
});
|
||||
|
||||
if (this.listeners.onSlowQuery) {
|
||||
this.listeners.onSlowQuery(key, duration);
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up
|
||||
this.queryTimings.delete(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Record a cache invalidation
|
||||
*/
|
||||
recordInvalidation(queryKey: string) {
|
||||
if (!this.enabled) return;
|
||||
|
||||
this.metrics.invalidations++;
|
||||
|
||||
logger.debug('Cache invalidated', { queryKey });
|
||||
|
||||
if (this.listeners.onInvalidation) {
|
||||
this.listeners.onInvalidation(queryKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current metrics
|
||||
*/
|
||||
getMetrics(): Readonly<CacheMetrics> {
|
||||
return { ...this.metrics };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get metrics as formatted string
|
||||
*/
|
||||
getMetricsReport(): string {
|
||||
const m = this.metrics;
|
||||
const uptimeMinutes = Math.round((Date.now() - m.lastReset.getTime()) / 1000 / 60);
|
||||
|
||||
return `
|
||||
Cache Performance Report
|
||||
========================
|
||||
Uptime: ${uptimeMinutes} minutes
|
||||
Total Queries: ${m.totalQueries}
|
||||
Cache Hits: ${m.hits} (${(m.hitRate * 100).toFixed(1)}%)
|
||||
Cache Misses: ${m.misses}
|
||||
Avg Query Time: ${Math.round(m.avgQueryTime)}ms
|
||||
Slow Queries: ${m.slowQueries}
|
||||
Invalidations: ${m.invalidations}
|
||||
`.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Log current metrics to console
|
||||
*/
|
||||
logMetrics() {
|
||||
console.log(this.getMetricsReport());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set slow query threshold (in milliseconds)
|
||||
*/
|
||||
setSlowQueryThreshold(ms: number) {
|
||||
this.slowQueryThreshold = ms;
|
||||
logger.info('Slow query threshold updated', { threshold: ms });
|
||||
}
|
||||
|
||||
/**
|
||||
* Register event listeners
|
||||
*/
|
||||
on(event: 'slowQuery', callback: (queryKey: string, duration: number) => void): void;
|
||||
on(event: 'cacheMiss', callback: (queryKey: string) => void): void;
|
||||
on(event: 'invalidation', callback: (queryKey: string) => void): void;
|
||||
on(event: string, callback: (...args: any[]) => void): void {
|
||||
if (event === 'slowQuery') {
|
||||
this.listeners.onSlowQuery = callback;
|
||||
} else if (event === 'cacheMiss') {
|
||||
this.listeners.onCacheMiss = callback;
|
||||
} else if (event === 'invalidation') {
|
||||
this.listeners.onInvalidation = callback;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup automatic tracking with QueryClient
|
||||
* @private
|
||||
*/
|
||||
private setupQueryClientTracking(queryClient: QueryClient) {
|
||||
const cache = queryClient.getQueryCache();
|
||||
|
||||
// Subscribe to cache updates
|
||||
const unsubscribe = cache.subscribe((event) => {
|
||||
if (!this.enabled) return;
|
||||
|
||||
const queryKey = this.normalizeQueryKey(event.query.queryKey);
|
||||
|
||||
if (event.type === 'updated') {
|
||||
const query = event.query;
|
||||
|
||||
// Check if this is a cache hit or miss
|
||||
if (query.state.dataUpdatedAt > 0) {
|
||||
const isCacheHit = query.state.fetchStatus !== 'fetching';
|
||||
|
||||
if (isCacheHit) {
|
||||
this.recordHit(queryKey);
|
||||
} else {
|
||||
this.recordMiss(queryKey);
|
||||
this.startQuery(queryKey);
|
||||
}
|
||||
}
|
||||
|
||||
// Record when fetch completes
|
||||
if (query.state.status === 'success' || query.state.status === 'error') {
|
||||
this.endQuery(queryKey, query.state.status);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Store unsubscribe function
|
||||
(this as any)._unsubscribe = unsubscribe;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize query key to string for tracking
|
||||
* @private
|
||||
*/
|
||||
private normalizeQueryKey(queryKey: string | readonly unknown[]): string {
|
||||
if (typeof queryKey === 'string') {
|
||||
return queryKey;
|
||||
}
|
||||
return JSON.stringify(queryKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update hit rate percentage
|
||||
* @private
|
||||
*/
|
||||
private updateHitRate() {
|
||||
if (this.metrics.totalQueries === 0) {
|
||||
this.metrics.hitRate = 0;
|
||||
} else {
|
||||
this.metrics.hitRate = this.metrics.hits / this.metrics.totalQueries;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset metrics to initial state
|
||||
* @private
|
||||
*/
|
||||
private resetMetrics(): CacheMetrics {
|
||||
return {
|
||||
hits: 0,
|
||||
misses: 0,
|
||||
hitRate: 0,
|
||||
totalQueries: 0,
|
||||
avgQueryTime: 0,
|
||||
slowQueries: 0,
|
||||
invalidations: 0,
|
||||
lastReset: new Date()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Singleton instance
|
||||
export const cacheMonitor = new CacheMonitor();
|
||||
|
||||
/**
|
||||
* Hook to use cache monitoring in React components
|
||||
* Only use for debugging - do not leave in production code
|
||||
*
|
||||
* @example
|
||||
* function DebugPanel() {
|
||||
* const metrics = useCacheMonitoring();
|
||||
*
|
||||
* return (
|
||||
* <div>
|
||||
* <h3>Cache Stats</h3>
|
||||
* <p>Hit Rate: {(metrics.hitRate * 100).toFixed(1)}%</p>
|
||||
* <p>Avg Query Time: {Math.round(metrics.avgQueryTime)}ms</p>
|
||||
* </div>
|
||||
* );
|
||||
* }
|
||||
*/
|
||||
export function useCacheMonitoring() {
|
||||
// Re-render when metrics change (simple polling)
|
||||
const [metrics, setMetrics] = React.useState(cacheMonitor.getMetrics());
|
||||
|
||||
React.useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setMetrics(cacheMonitor.getMetrics());
|
||||
}, 1000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
return metrics;
|
||||
}
|
||||
|
||||
// Only import React if using the hook
|
||||
let React: any;
|
||||
try {
|
||||
React = require('react');
|
||||
} catch {
|
||||
// React not available, hook won't work but main exports still functional
|
||||
}
|
||||
@@ -223,76 +223,6 @@ export async function connectIdentity(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Link an OAuth identity to the logged-in user's account (Manual Linking)
|
||||
* Requires user to be authenticated
|
||||
*/
|
||||
export async function linkOAuthIdentity(
|
||||
provider: OAuthProvider
|
||||
): Promise<IdentityOperationResult> {
|
||||
try {
|
||||
const { data, error } = await supabase.auth.linkIdentity({
|
||||
provider
|
||||
});
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
// Log audit event
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
if (user) {
|
||||
await logIdentityChange(user.id, 'identity_linked', {
|
||||
provider,
|
||||
method: 'manual',
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
const errorMsg = getErrorMessage(error);
|
||||
logger.error('Failed to link identity', {
|
||||
action: 'identity_link',
|
||||
provider,
|
||||
error: errorMsg
|
||||
});
|
||||
return {
|
||||
success: false,
|
||||
error: errorMsg
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log when automatic identity linking occurs
|
||||
* Called internally when Supabase automatically links identities
|
||||
*/
|
||||
export async function logAutomaticIdentityLinking(
|
||||
userId: string,
|
||||
provider: OAuthProvider,
|
||||
email: string
|
||||
): Promise<void> {
|
||||
try {
|
||||
await logIdentityChange(userId, 'identity_auto_linked', {
|
||||
provider,
|
||||
email,
|
||||
method: 'automatic',
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
|
||||
logger.info('Automatic identity linking logged', {
|
||||
userId,
|
||||
provider,
|
||||
action: 'identity_auto_linked'
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('Failed to log automatic identity linking', {
|
||||
userId,
|
||||
provider,
|
||||
error: getErrorMessage(error)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Add password authentication to an OAuth-only account
|
||||
|
||||
@@ -95,70 +95,6 @@ export function useQueryInvalidation() {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Invalidate user profile cache
|
||||
* Call this after profile updates
|
||||
*/
|
||||
invalidateUserProfile: (userId: string) => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.profile.detail(userId) });
|
||||
},
|
||||
|
||||
/**
|
||||
* Invalidate profile stats cache
|
||||
* Call this after profile-related changes
|
||||
*/
|
||||
invalidateProfileStats: (userId: string) => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.profile.stats(userId) });
|
||||
},
|
||||
|
||||
/**
|
||||
* Invalidate profile activity cache
|
||||
* Call this after activity changes
|
||||
*/
|
||||
invalidateProfileActivity: (userId: string) => {
|
||||
queryClient.invalidateQueries({ queryKey: ['profile', 'activity', userId] });
|
||||
},
|
||||
|
||||
/**
|
||||
* Invalidate user search results
|
||||
* Call this when display names change
|
||||
*/
|
||||
invalidateUserSearch: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['users', 'search'] });
|
||||
},
|
||||
|
||||
/**
|
||||
* Invalidate admin settings cache
|
||||
* Call this after updating admin settings
|
||||
*/
|
||||
invalidateAdminSettings: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.admin.settings() });
|
||||
},
|
||||
|
||||
/**
|
||||
* Invalidate audit logs cache
|
||||
* Call this after inserting audit log entries
|
||||
*/
|
||||
invalidateAuditLogs: (userId?: string) => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.admin.auditLogs(userId) });
|
||||
},
|
||||
|
||||
/**
|
||||
* Invalidate contact submissions cache
|
||||
* Call this after updating contact submissions
|
||||
*/
|
||||
invalidateContactSubmissions: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ['admin-contact-submissions'] });
|
||||
},
|
||||
|
||||
/**
|
||||
* Invalidate blog posts cache
|
||||
* Call this after creating/updating/deleting blog posts
|
||||
*/
|
||||
invalidateBlogPosts: () => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.admin.blogPosts() });
|
||||
},
|
||||
|
||||
/**
|
||||
* Invalidate parks listing cache
|
||||
* Call this after creating/updating/deleting parks
|
||||
@@ -250,147 +186,5 @@ export function useQueryInvalidation() {
|
||||
queryKey: ['homepage', 'featured-parks']
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Invalidate company detail cache
|
||||
* Call this after updating a company
|
||||
*/
|
||||
invalidateCompanyDetail: (slug: string, type: string) => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.companies.detail(slug, type) });
|
||||
},
|
||||
|
||||
/**
|
||||
* Invalidate company statistics cache
|
||||
* Call this after changes affecting company stats
|
||||
*/
|
||||
invalidateCompanyStatistics: (id: string, type: string) => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.companies.statistics(id, type) });
|
||||
},
|
||||
|
||||
/**
|
||||
* Invalidate company parks cache
|
||||
* Call this after park changes
|
||||
*/
|
||||
invalidateCompanyParks: (id: string, type: 'operator' | 'property_owner') => {
|
||||
queryClient.invalidateQueries({ queryKey: ['companies', 'parks', id, type] });
|
||||
},
|
||||
|
||||
/**
|
||||
* Invalidate ride model detail cache
|
||||
* Call this after updating a ride model
|
||||
*/
|
||||
invalidateRideModelDetail: (manufacturerSlug: string, modelSlug: string) => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.rideModels.detail(manufacturerSlug, modelSlug) });
|
||||
},
|
||||
|
||||
/**
|
||||
* Invalidate ride model statistics cache
|
||||
* Call this after changes affecting model stats
|
||||
*/
|
||||
invalidateRideModelStatistics: (modelId: string) => {
|
||||
queryClient.invalidateQueries({ queryKey: queryKeys.rideModels.statistics(modelId) });
|
||||
},
|
||||
|
||||
/**
|
||||
* Invalidate model rides cache
|
||||
* Call this after ride changes
|
||||
*/
|
||||
invalidateModelRides: (modelId: string, limit?: number) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.rideModels.rides(modelId, limit),
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Invalidate entity name cache
|
||||
* Call this after updating an entity's name
|
||||
*/
|
||||
invalidateEntityName: (entityType: string, entityId: string) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.entities.name(entityType, entityId)
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Invalidate blog post cache
|
||||
* Call this after updating a blog post
|
||||
*/
|
||||
invalidateBlogPost: (slug: string) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.blog.post(slug)
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Invalidate coaster stats cache
|
||||
* Call this after updating ride statistics
|
||||
*/
|
||||
invalidateCoasterStats: (rideId: string) => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.stats.coaster(rideId)
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Invalidate email change status cache
|
||||
* Call this after email change operations
|
||||
*/
|
||||
invalidateEmailChangeStatus: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.security.emailChangeStatus()
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Invalidate sessions cache
|
||||
* Call this after session operations (login, logout, revoke)
|
||||
*/
|
||||
invalidateSessions: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.security.sessions()
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Invalidate security queries
|
||||
* Call this after security-related changes (email, sessions)
|
||||
*/
|
||||
invalidateSecurityQueries: () => {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.security.emailChangeStatus()
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.security.sessions()
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Smart invalidation for related entities
|
||||
* Invalidates entity detail, photos, reviews, and name cache
|
||||
* Call this after any entity update
|
||||
*/
|
||||
invalidateRelatedEntities: (entityType: string, entityId: string) => {
|
||||
// Invalidate the entity itself
|
||||
if (entityType === 'park') {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.parks.detail(entityId)
|
||||
});
|
||||
} else if (entityType === 'ride') {
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.rides.detail('', entityId)
|
||||
});
|
||||
}
|
||||
|
||||
// Invalidate photos, reviews, and entity name
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.photos.entity(entityType, entityId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.reviews.entity(entityType as 'park' | 'ride', entityId)
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: queryKeys.entities.name(entityType, entityId)
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -62,8 +62,8 @@ export const queryKeys = {
|
||||
|
||||
// Photos queries
|
||||
photos: {
|
||||
entity: (entityType: string, entityId: string, sortBy?: string) =>
|
||||
['photos', entityType, entityId, sortBy] as const,
|
||||
entity: (entityType: string, entityId: string) =>
|
||||
['photos', entityType, entityId] as const,
|
||||
count: (entityType: string, entityId: string) =>
|
||||
['photos', 'count', entityType, entityId] as const,
|
||||
},
|
||||
@@ -76,81 +76,5 @@ export const queryKeys = {
|
||||
// Lists queries
|
||||
lists: {
|
||||
items: (listId: string) => ['list-items', listId] as const,
|
||||
user: (userId?: string) => ['lists', 'user', userId] as const,
|
||||
},
|
||||
|
||||
// Users queries
|
||||
users: {
|
||||
roles: (userId?: string) => ['users', 'roles', userId] as const,
|
||||
search: (searchTerm: string) => ['users', 'search', searchTerm] as const,
|
||||
},
|
||||
|
||||
// Admin queries
|
||||
admin: {
|
||||
versionAudit: ['admin', 'version-audit'] as const,
|
||||
settings: () => ['admin-settings'] as const,
|
||||
blogPosts: () => ['admin-blog-posts'] as const,
|
||||
contactSubmissions: (statusFilter?: string, categoryFilter?: string, searchQuery?: string, showArchived?: boolean) =>
|
||||
['admin-contact-submissions', statusFilter, categoryFilter, searchQuery, showArchived] as const,
|
||||
auditLogs: (userId?: string) => ['admin', 'audit-logs', userId] as const,
|
||||
},
|
||||
|
||||
// Moderation queries
|
||||
moderation: {
|
||||
photoSubmission: (submissionId?: string) => ['moderation', 'photo-submission', submissionId] as const,
|
||||
recentActivity: ['moderation', 'recent-activity'] as const,
|
||||
},
|
||||
|
||||
// Company queries
|
||||
companies: {
|
||||
all: (type: string) => ['companies', 'all', type] as const,
|
||||
detail: (slug: string, type: string) => ['companies', 'detail', slug, type] as const,
|
||||
statistics: (id: string, type: string) => ['companies', 'statistics', id, type] as const,
|
||||
parks: (id: string, type: string, limit: number) => ['companies', 'parks', id, type, limit] as const,
|
||||
},
|
||||
|
||||
// Profile queries
|
||||
profile: {
|
||||
detail: (userId: string) => ['profile', userId] as const,
|
||||
activity: (userId: string, isOwn: boolean, isMod: boolean) =>
|
||||
['profile', 'activity', userId, isOwn, isMod] as const,
|
||||
stats: (userId: string) => ['profile', 'stats', userId] as const,
|
||||
},
|
||||
|
||||
// Ride Models queries
|
||||
rideModels: {
|
||||
all: (manufacturerId: string) => ['ride-models', 'all', manufacturerId] as const,
|
||||
detail: (manufacturerSlug: string, modelSlug: string) =>
|
||||
['ride-models', 'detail', manufacturerSlug, modelSlug] as const,
|
||||
rides: (modelId: string, limit?: number) =>
|
||||
['ride-models', 'rides', modelId, limit] as const,
|
||||
statistics: (modelId: string) => ['ride-models', 'statistics', modelId] as const,
|
||||
},
|
||||
|
||||
// Settings queries
|
||||
settings: {
|
||||
publicNovu: () => ['public-novu-settings'] as const,
|
||||
},
|
||||
|
||||
// Stats queries
|
||||
stats: {
|
||||
coaster: (rideId: string) => ['coaster-stats', rideId] as const,
|
||||
},
|
||||
|
||||
// Blog queries
|
||||
blog: {
|
||||
post: (slug: string) => ['blog-post', slug] as const,
|
||||
viewIncrement: (slug: string) => ['blog-view-increment', slug] as const,
|
||||
},
|
||||
|
||||
// Entity name queries (for PhotoManagementDialog)
|
||||
entities: {
|
||||
name: (entityType: string, entityId: string) => ['entity-name', entityType, entityId] as const,
|
||||
},
|
||||
|
||||
// Security queries
|
||||
security: {
|
||||
emailChangeStatus: () => ['email-change-status'] as const,
|
||||
sessions: () => ['my-sessions'] as const,
|
||||
},
|
||||
} as const;
|
||||
|
||||
Reference in New Issue
Block a user