From 9bcf5ace5d1befa4dbe8698583497333c29e670c Mon Sep 17 00:00:00 2001 From: Kyle Pope Date: Thu, 5 Mar 2026 20:02:52 +0800 Subject: [PATCH] Fix search cold-cache gate, 429 handling, and datetime.now() violations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ConnectionSearch.tsx: - Add loading guard for useSettings() — prevents cold cache from showing "enable connections" gate when settings haven't loaded yet - Add 429 rate limit handling in search catch block — shows user-friendly message instead of silently showing "User not found" - Import axios for isAxiosError type guard connections.py: - Fix 3x datetime.now() → datetime.now(timezone.utc) per hard rule (lines 187, 378, 565) Co-Authored-By: Claude Opus 4.6 --- backend/app/routers/connections.py | 6 +++--- .../components/connections/ConnectionSearch.tsx | 15 +++++++++++---- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/backend/app/routers/connections.py b/backend/app/routers/connections.py index aa7de36..a566f27 100644 --- a/backend/app/routers/connections.py +++ b/backend/app/routers/connections.py @@ -184,7 +184,7 @@ async def send_connection_request( raise HTTPException(status_code=409, detail="A pending request already exists") # Per-receiver cap: max 5 pending requests within 10 minutes - ten_min_ago = datetime.now() - timedelta(minutes=10) + ten_min_ago = datetime.now(timezone.utc) - timedelta(minutes=10) pending_count = await db.scalar( select(func.count()) .select_from(ConnectionRequest) @@ -375,7 +375,7 @@ async def _respond_to_request_inner( db: AsyncSession, current_user: User, ) -> RespondAcceptResponse | RespondRejectResponse: - now = datetime.now() + now = datetime.now(timezone.utc) # Atomic update — only succeeds if status is still 'pending' and receiver is current user result = await db.execute( @@ -562,7 +562,7 @@ async def cancel_request( current_user: User = Depends(get_current_user), ): """Cancel an outgoing connection request. Atomic via UPDATE...WHERE status='pending'.""" - now = datetime.now() + now = datetime.now(timezone.utc) # Atomic update — only succeeds if sender is current user and status is still pending result = await db.execute( diff --git a/frontend/src/components/connections/ConnectionSearch.tsx b/frontend/src/components/connections/ConnectionSearch.tsx index 9dcf54f..40da4ae 100644 --- a/frontend/src/components/connections/ConnectionSearch.tsx +++ b/frontend/src/components/connections/ConnectionSearch.tsx @@ -14,6 +14,7 @@ import { Button } from '@/components/ui/button'; import { Label } from '@/components/ui/label'; import { useConnections } from '@/hooks/useConnections'; import { useSettings } from '@/hooks/useSettings'; +import axios from 'axios'; import { getErrorMessage } from '@/lib/api'; interface ConnectionSearchProps { @@ -24,7 +25,7 @@ interface ConnectionSearchProps { export default function ConnectionSearch({ open, onOpenChange, personId }: ConnectionSearchProps) { const { search, isSearching, sendRequest, isSending } = useConnections(); - const { settings } = useSettings(); + const { settings, isLoading: isLoadingSettings } = useSettings(); const navigate = useNavigate(); const [umbralName, setUmbralName] = useState(''); const [found, setFound] = useState(null); @@ -39,8 +40,12 @@ export default function ConnectionSearch({ open, onOpenChange, personId }: Conne try { const result = await search(umbralName.trim()); setFound(result.found); - } catch { - setFound(false); + } catch (err) { + if (axios.isAxiosError(err) && err.response?.status === 429) { + toast.error('Too many searches — please wait a moment and try again'); + } else { + setFound(false); + } } }; @@ -76,7 +81,9 @@ export default function ConnectionSearch({ open, onOpenChange, personId }: Conne
- {!acceptConnectionsEnabled ? ( + {isLoadingSettings ? ( +
+ ) : !acceptConnectionsEnabled ? (