mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-26 11:06:59 -05:00
Add audit logging for admin settings changes, rate limit config updates, anomaly detection config changes (skipped due to no UI), and version cleanup settings updates. Implement logging via central logAdminAction helper and integrate into AdminSettings, VersionCleanupSettings, and RateLimitAlerts mutations (create, update, delete).
241 lines
7.9 KiB
TypeScript
241 lines
7.9 KiB
TypeScript
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
import { supabase } from '@/lib/supabaseClient';
|
|
import { useAuth } from './useAuth';
|
|
import { useUserRole } from './useUserRole';
|
|
import { useToast } from './use-toast';
|
|
import { useCallback, useMemo } from 'react';
|
|
import type { Json } from '@/integrations/supabase/types';
|
|
import { handleError } from '@/lib/errorHandler';
|
|
|
|
interface AdminSetting {
|
|
id: string;
|
|
setting_key: string;
|
|
setting_value: unknown;
|
|
category: string;
|
|
description: string;
|
|
}
|
|
|
|
export function useAdminSettings() {
|
|
const { user } = useAuth();
|
|
const { isSuperuser } = useUserRole();
|
|
const { toast } = useToast();
|
|
const queryClient = useQueryClient();
|
|
|
|
const {
|
|
data: settings,
|
|
isLoading,
|
|
error
|
|
} = useQuery({
|
|
queryKey: ['admin-settings'],
|
|
queryFn: async () => {
|
|
const { data, error } = await supabase
|
|
.from('admin_settings')
|
|
.select('*')
|
|
.order('category', { ascending: true });
|
|
|
|
if (error) throw error;
|
|
return data as AdminSetting[];
|
|
},
|
|
enabled: !!user && isSuperuser()
|
|
});
|
|
|
|
// Memoize settings into a stable map to prevent cascading re-renders
|
|
const settingsMap = useMemo(() => {
|
|
if (!settings) return {};
|
|
return Object.fromEntries(
|
|
settings.map(s => [s.setting_key, s.setting_value])
|
|
);
|
|
}, [settings]);
|
|
|
|
const updateSettingMutation = useMutation({
|
|
mutationFn: async ({ key, value }: { key: string; value: unknown }) => {
|
|
// Get old value for audit log
|
|
const oldSetting = settings?.find(s => s.setting_key === key);
|
|
const oldValue = oldSetting?.setting_value;
|
|
|
|
const { error } = await supabase
|
|
.from('admin_settings')
|
|
.update({
|
|
setting_value: value as Json,
|
|
updated_by: user?.id,
|
|
updated_at: new Date().toISOString()
|
|
})
|
|
.eq('setting_key', key);
|
|
|
|
if (error) throw error;
|
|
return { key, value, oldValue };
|
|
},
|
|
onSuccess: async (data) => {
|
|
queryClient.invalidateQueries({ queryKey: ['admin-settings'] });
|
|
|
|
// Log to audit trail
|
|
const { logAdminAction } = await import('@/lib/adminActionAuditHelpers');
|
|
await logAdminAction('admin_setting_updated', {
|
|
setting_key: data.key,
|
|
old_value: data.oldValue,
|
|
new_value: data.value,
|
|
});
|
|
|
|
toast({
|
|
title: "Setting Updated",
|
|
description: "The setting has been saved successfully.",
|
|
});
|
|
},
|
|
onError: (error: Error, variables) => {
|
|
handleError(error, {
|
|
action: 'Update Admin Setting',
|
|
userId: user?.id,
|
|
metadata: {
|
|
settingKey: variables.key,
|
|
attemptedValue: variables.value
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
const getSettingValue = useCallback((key: string, defaultValue: unknown = null) => {
|
|
return settingsMap[key] ?? defaultValue;
|
|
}, [settingsMap]);
|
|
|
|
const updateSetting = async (key: string, value: unknown) => {
|
|
return updateSettingMutation.mutateAsync({ key, value });
|
|
};
|
|
|
|
// Helper functions for common settings (memoized with useCallback for stable references)
|
|
const getAutoFlagThreshold = useCallback(() => {
|
|
return parseInt(String(getSettingValue('moderation.auto_flag_threshold', '3')));
|
|
}, [getSettingValue]);
|
|
|
|
const getRequireApproval = useCallback(() => {
|
|
const value = getSettingValue('moderation.require_approval', 'true');
|
|
return value === true || value === 'true';
|
|
}, [getSettingValue]);
|
|
|
|
const getBanDurations = useCallback(() => {
|
|
const value = getSettingValue('moderation.ban_durations', ['1d', '7d', '30d', 'permanent']);
|
|
return Array.isArray(value) ? value : JSON.parse(String(value || '[]'));
|
|
}, [getSettingValue]);
|
|
|
|
const getEmailAlertsEnabled = useCallback(() => {
|
|
const value = getSettingValue('notifications.email_alerts', 'true');
|
|
return value === true || value === 'true';
|
|
}, [getSettingValue]);
|
|
|
|
const getReportThreshold = useCallback(() => {
|
|
return parseInt(String(getSettingValue('notifications.report_threshold', '5')));
|
|
}, [getSettingValue]);
|
|
|
|
const getAuditRetentionDays = useCallback(() => {
|
|
return parseInt(String(getSettingValue('system.audit_retention_days', '365')));
|
|
}, [getSettingValue]);
|
|
|
|
const getAutoCleanupEnabled = useCallback(() => {
|
|
const value = getSettingValue('system.auto_cleanup', 'false');
|
|
return value === true || value === 'true';
|
|
}, [getSettingValue]);
|
|
|
|
const getAdminPanelRefreshMode = useCallback((): 'auto' | 'manual' => {
|
|
const value = getSettingValue('system.admin_panel_refresh_mode', 'auto');
|
|
// Remove quotes if they exist (JSON string stored in DB)
|
|
const cleanValue = typeof value === 'string' ? value.replace(/"/g, '') : String(value);
|
|
return (cleanValue === 'manual' ? 'manual' : 'auto') as 'auto' | 'manual';
|
|
}, [getSettingValue]);
|
|
|
|
const getAdminPanelPollInterval = useCallback(() => {
|
|
const value = getSettingValue('system.admin_panel_poll_interval', 30);
|
|
return parseInt(value?.toString() || '30') * 1000; // Convert to milliseconds
|
|
}, [getSettingValue]);
|
|
|
|
/**
|
|
* Get auto-refresh strategy setting
|
|
* Returns: 'merge' | 'replace' | 'notify'
|
|
*/
|
|
const getAutoRefreshStrategy = useCallback((): 'merge' | 'replace' | 'notify' => {
|
|
const value = getSettingValue('auto_refresh_strategy', 'merge');
|
|
const cleanValue = typeof value === 'string' ? value.replace(/"/g, '') : value;
|
|
return cleanValue as 'merge' | 'replace' | 'notify';
|
|
}, [getSettingValue]);
|
|
|
|
/**
|
|
* Get preserve interaction state setting
|
|
* Returns: boolean
|
|
*/
|
|
const getPreserveInteractionState = useCallback((): boolean => {
|
|
const value = getSettingValue('preserve_interaction_state', 'true');
|
|
const cleanValue = typeof value === 'string' ? value.replace(/"/g, '') : value;
|
|
return cleanValue === 'true' || cleanValue === true;
|
|
}, [getSettingValue]);
|
|
|
|
const getNotificationRecipients = useCallback(() => {
|
|
return getSettingValue('notifications.recipients', []);
|
|
}, [getSettingValue]);
|
|
|
|
const getUseRealtimeQueue = useCallback((): boolean => {
|
|
const value = getSettingValue('system.use_realtime_queue', 'true');
|
|
const cleanValue = typeof value === 'string' ? value.replace(/"/g, '') : value;
|
|
return cleanValue === 'true' || cleanValue === true;
|
|
}, [getSettingValue]);
|
|
|
|
const getRefreshOnTabVisible = useCallback((): boolean => {
|
|
const value = getSettingValue('system.refresh_on_tab_visible', 'false');
|
|
const cleanValue = typeof value === 'string' ? value.replace(/"/g, '') : value;
|
|
return cleanValue === 'true' || cleanValue === true;
|
|
}, [getSettingValue]);
|
|
|
|
const getSetting = useCallback((key: string) => {
|
|
return settings?.find(s => s.setting_key === key);
|
|
}, [settings]);
|
|
|
|
const getSettingsByCategory = useCallback((category: string) => {
|
|
return settings?.filter(s => s.category === category) || [];
|
|
}, [settings]);
|
|
|
|
return useMemo(() => ({
|
|
settings,
|
|
isLoading,
|
|
error,
|
|
updateSetting,
|
|
isUpdating: updateSettingMutation.isPending,
|
|
getSetting,
|
|
getSettingValue,
|
|
getSettingsByCategory,
|
|
// Helper functions
|
|
getAutoFlagThreshold,
|
|
getRequireApproval,
|
|
getBanDurations,
|
|
getEmailAlertsEnabled,
|
|
getNotificationRecipients,
|
|
getReportThreshold,
|
|
getAuditRetentionDays,
|
|
getAutoCleanupEnabled,
|
|
getAdminPanelRefreshMode,
|
|
getAdminPanelPollInterval,
|
|
getAutoRefreshStrategy,
|
|
getPreserveInteractionState,
|
|
getUseRealtimeQueue,
|
|
getRefreshOnTabVisible,
|
|
}), [
|
|
settings,
|
|
isLoading,
|
|
error,
|
|
updateSetting,
|
|
updateSettingMutation.isPending,
|
|
getSetting,
|
|
getSettingValue,
|
|
getSettingsByCategory,
|
|
getAutoFlagThreshold,
|
|
getRequireApproval,
|
|
getBanDurations,
|
|
getEmailAlertsEnabled,
|
|
getNotificationRecipients,
|
|
getReportThreshold,
|
|
getAuditRetentionDays,
|
|
getAutoCleanupEnabled,
|
|
getAdminPanelRefreshMode,
|
|
getAdminPanelPollInterval,
|
|
getAutoRefreshStrategy,
|
|
getPreserveInteractionState,
|
|
getUseRealtimeQueue,
|
|
getRefreshOnTabVisible,
|
|
]);
|
|
} |