feat: Implement complete API optimization plan

This commit is contained in:
gpt-engineer-app[bot]
2025-10-31 12:28:24 +00:00
parent 631ce9c89e
commit ca9aa757ae
9 changed files with 363 additions and 333 deletions

View File

@@ -1,52 +1,72 @@
import { useQuery } from '@tanstack/react-query';
import { supabase } from '@/integrations/supabase/client';
import { useAuth } from '@/hooks/useAuth';
import { logger } from '@/lib/logger';
import type { UserBlock } from '@/types/privacy';
/**
* Hook for querying blocked users
* Provides: list of blocked users with profile information
* Hook to fetch blocked users for the authenticated user
* Provides: automatic caching, refetch on window focus, and loading states
*/
export function useBlockedUsers() {
const { user } = useAuth();
export function useBlockedUsers(userId?: string) {
return useQuery({
queryKey: ['blocked-users', user?.id],
queryKey: ['blocked-users', userId],
queryFn: async () => {
if (!user) return [];
if (!userId) throw new Error('User ID required');
// First get the blocked user IDs
// Fetch blocked user IDs
const { data: blocks, error: blocksError } = await supabase
.from('user_blocks')
.select('id, blocked_id, reason, created_at')
.eq('blocker_id', user.id)
.eq('blocker_id', userId)
.order('created_at', { ascending: false });
if (blocksError) throw blocksError;
if (blocksError) {
logger.error('Failed to fetch user blocks', {
userId,
action: 'fetch_blocked_users',
error: blocksError.message,
errorCode: blocksError.code
});
throw blocksError;
}
if (!blocks || blocks.length === 0) {
return [];
}
// Then get the profile information for blocked users
// Fetch profile information for blocked users
const blockedIds = blocks.map(b => b.blocked_id);
const { data: profiles, error: profilesError } = await supabase
.from('profiles')
.select('user_id, username, display_name, avatar_url')
.in('user_id', blockedIds);
if (profilesError) throw profilesError;
if (profilesError) {
logger.error('Failed to fetch blocked user profiles', {
userId,
action: 'fetch_blocked_user_profiles',
error: profilesError.message,
errorCode: profilesError.code
});
throw profilesError;
}
// Combine the data
const blockedUsersWithProfiles: UserBlock[] = blocks.map(block => ({
...block,
blocker_id: user.id,
blocker_id: userId,
blocked_profile: profiles?.find(p => p.user_id === block.blocked_id)
}));
logger.info('Blocked users fetched successfully', {
userId,
action: 'fetch_blocked_users',
count: blockedUsersWithProfiles.length
});
return blockedUsersWithProfiles;
},
enabled: !!user,
staleTime: 5 * 60 * 1000, // 5 minutes
enabled: !!userId,
staleTime: 1000 * 60 * 5, // 5 minutes
});
}

View File

@@ -0,0 +1,50 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { supabase } from '@/integrations/supabase/client';
import { toast } from 'sonner';
import { getErrorMessage } from '@/lib/errorHandler';
import { useQueryInvalidation } from '@/lib/queryInvalidation';
interface ReorderCreditParams {
creditId: string;
newPosition: number;
}
/**
* Hook for ride credits mutations
* Provides: reorder ride credits with automatic cache invalidation
*/
export function useRideCreditsMutation() {
const queryClient = useQueryClient();
const { invalidateRideDetail } = useQueryInvalidation();
const reorderCredit = useMutation({
mutationFn: async ({ creditId, newPosition }: ReorderCreditParams) => {
const { error } = await supabase.rpc('reorder_ride_credit', {
p_credit_id: creditId,
p_new_position: newPosition
});
if (error) throw error;
return { creditId, newPosition };
},
onError: (error: unknown) => {
toast.error("Reorder Failed", {
description: getErrorMessage(error),
});
},
onSuccess: () => {
// Invalidate ride credits queries
queryClient.invalidateQueries({ queryKey: ['ride-credits'] });
toast.success("Order Updated", {
description: "Ride credit order has been saved.",
});
},
});
return {
reorderCredit,
isReordering: reorderCredit.isPending,
};
}

View File

@@ -0,0 +1,83 @@
import { useMutation } from '@tanstack/react-query';
import { supabase } from '@/integrations/supabase/client';
import { toast } from 'sonner';
import { getErrorMessage } from '@/lib/errorHandler';
import { useQueryInvalidation } from '@/lib/queryInvalidation';
import { notificationService } from '@/lib/notificationService';
import { logger } from '@/lib/logger';
interface EmailChangeParams {
newEmail: string;
currentEmail: string;
userId: string;
}
/**
* Hook for email change mutations
* Provides: email changes with automatic audit logging and cache invalidation
*/
export function useEmailChangeMutation() {
const { invalidateAuditLogs } = useQueryInvalidation();
const changeEmail = useMutation({
mutationFn: async ({ newEmail, currentEmail, userId }: EmailChangeParams) => {
// Update email address
const { error: updateError } = await supabase.auth.updateUser({
email: newEmail
});
if (updateError) throw updateError;
// Log the email change attempt
await supabase.from('admin_audit_log').insert({
admin_user_id: userId,
target_user_id: userId,
action: 'email_change_initiated',
details: {
old_email: currentEmail,
new_email: newEmail,
timestamp: new Date().toISOString(),
}
});
// Send security notifications (non-blocking)
if (notificationService.isEnabled()) {
notificationService.trigger({
workflowId: 'security-alert',
subscriberId: userId,
payload: {
alert_type: 'email_change_initiated',
old_email: currentEmail,
new_email: newEmail,
timestamp: new Date().toISOString(),
}
}).catch(error => {
logger.error('Failed to send security notification', {
userId,
action: 'email_change_notification',
error: error instanceof Error ? error.message : String(error)
});
});
}
return { newEmail };
},
onError: (error: unknown) => {
toast.error("Update Failed", {
description: getErrorMessage(error),
});
},
onSuccess: (_data, { userId }) => {
invalidateAuditLogs(userId);
toast.success("Email Change Initiated", {
description: "Check both email addresses for confirmation links.",
});
},
});
return {
changeEmail,
isChanging: changeEmail.isPending,
};
}

View File

@@ -0,0 +1,87 @@
import { useMutation } from '@tanstack/react-query';
import { supabase } from '@/integrations/supabase/client';
import { toast } from 'sonner';
import { getErrorMessage } from '@/lib/errorHandler';
import { useQueryInvalidation } from '@/lib/queryInvalidation';
import { invokeWithTracking } from '@/lib/edgeFunctionTracking';
import { logger } from '@/lib/logger';
interface PasswordUpdateParams {
password: string;
hasMFA: boolean;
userId: string;
}
/**
* Hook for password update mutations
* Provides: password updates with automatic audit logging and cache invalidation
*/
export function usePasswordUpdateMutation() {
const { invalidateAuditLogs } = useQueryInvalidation();
const updatePassword = useMutation({
mutationFn: async ({ password, hasMFA, userId }: PasswordUpdateParams) => {
// Update password
const { error: updateError } = await supabase.auth.updateUser({
password
});
if (updateError) throw updateError;
// Log audit trail
await supabase.from('admin_audit_log').insert({
admin_user_id: userId,
target_user_id: userId,
action: 'password_changed',
details: {
timestamp: new Date().toISOString(),
method: hasMFA ? 'password_with_mfa' : 'password_only',
user_agent: navigator.userAgent
}
});
// Send security notification (non-blocking)
try {
await invokeWithTracking(
'trigger-notification',
{
workflowId: 'security-alert',
subscriberId: userId,
payload: {
alert_type: 'password_changed',
timestamp: new Date().toISOString(),
device: navigator.userAgent.split(' ')[0]
}
},
userId
);
} catch (notifError) {
logger.error('Failed to send password change notification', {
userId,
action: 'password_change_notification',
error: getErrorMessage(notifError)
});
// Don't fail the password update if notification fails
}
return { success: true };
},
onError: (error: unknown) => {
toast.error("Update Failed", {
description: getErrorMessage(error),
});
},
onSuccess: (_data, { userId }) => {
invalidateAuditLogs(userId);
toast.success("Password Updated", {
description: "Your password has been successfully changed.",
});
},
});
return {
updatePassword,
isUpdating: updatePassword.isPending,
};
}