From e911810157fdaed8f2887cb7bea77dc80ee98057 Mon Sep 17 00:00:00 2001 From: Jake Kasper Date: Sat, 23 Aug 2025 14:11:49 -0400 Subject: [PATCH] rate limiting --- .../src/pages/Applications/Applications.js | 24 ++++++++++++++----- frontend/src/services/api.js | 18 ++++++++++++++ 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/frontend/src/pages/Applications/Applications.js b/frontend/src/pages/Applications/Applications.js index 6685e8f..3f52cfe 100644 --- a/frontend/src/pages/Applications/Applications.js +++ b/frontend/src/pages/Applications/Applications.js @@ -23,6 +23,7 @@ const Applications = () => { const [nozzles, setNozzles] = useState([]); const [selectedPropertyDetails, setSelectedPropertyDetails] = useState(null); const [editingPlan, setEditingPlan] = useState(null); + const [propertyCache, setPropertyCache] = useState({}); useEffect(() => { fetchApplications(); @@ -76,9 +77,18 @@ const Applications = () => { }; const fetchPropertyDetails = async (propertyId) => { + // Check cache first + if (propertyCache[propertyId]) { + setSelectedPropertyDetails(propertyCache[propertyId]); + return propertyCache[propertyId]; + } + try { const response = await propertiesAPI.getById(parseInt(propertyId)); const property = response.data.data.property; + + // Cache the result + setPropertyCache(prev => ({ ...prev, [propertyId]: property })); setSelectedPropertyDetails(property); return property; } catch (error) { @@ -385,11 +395,8 @@ const ApplicationPlanModal = ({ // Initialize form with editing data useEffect(() => { - if (editingPlan) { + if (editingPlan && products.length > 0) { const propertyId = editingPlan.section?.propertyId || editingPlan.property?.id; - if (propertyId && propertyId !== planData.propertyId) { - onPropertySelect(propertyId); - } // Find the product from the plans products array const planProduct = editingPlan.products?.[0]; @@ -413,12 +420,17 @@ const ApplicationPlanModal = ({ plannedDate: editingPlan.plannedDate ? new Date(editingPlan.plannedDate).toISOString().split('T')[0] : '', notes: editingPlan.notes || '' }); + + // Only fetch property details if we don't already have them + if (propertyId && (!selectedPropertyDetails || selectedPropertyDetails.id !== propertyId)) { + onPropertySelect(propertyId); + } } - }, [editingPlan, products, onPropertySelect]); + }, [editingPlan, products]); const handlePropertyChange = async (propertyId) => { setPlanData({ ...planData, propertyId, selectedAreas: [] }); - if (propertyId) { + if (propertyId && propertyId !== selectedPropertyDetails?.id?.toString()) { setLoadingProperty(true); await onPropertySelect(propertyId); setLoadingProperty(false); diff --git a/frontend/src/services/api.js b/frontend/src/services/api.js index 4fef25a..0919b8b 100644 --- a/frontend/src/services/api.js +++ b/frontend/src/services/api.js @@ -13,6 +13,24 @@ const apiClient = axios.create({ }, }); +// Rate limiting - simple queue to prevent excessive requests +let lastRequestTime = 0; +const MIN_REQUEST_INTERVAL = 100; // 100ms between requests + +// Add rate limiting interceptor +apiClient.interceptors.request.use(async (config) => { + const now = Date.now(); + const timeSinceLastRequest = now - lastRequestTime; + + if (timeSinceLastRequest < MIN_REQUEST_INTERVAL) { + const delay = MIN_REQUEST_INTERVAL - timeSinceLastRequest; + await new Promise(resolve => setTimeout(resolve, delay)); + } + + lastRequestTime = Date.now(); + return config; +}); + // Request interceptor to add auth token apiClient.interceptors.request.use( (config) => {