ATlast — you'll never need to find your favorites on another platform again. Find your favs in the ATmosphere.
atproto

fix: prevent duplicate file upload

+40 -9
+19
netlify/functions/save-results.ts
··· 35 35 } 36 36 37 37 export const handler: Handler = async (event: HandlerEvent): Promise<HandlerResponse> => { 38 + 38 39 if (event.httpMethod !== 'POST') { 39 40 return { 40 41 statusCode: 405, ··· 80 81 81 82 const sql = getDbClient(); 82 83 let matchedCount = 0; 84 + 85 + // Check for recent uploads from this user 86 + const recentUpload = await sql` 87 + SELECT upload_id FROM user_uploads 88 + WHERE did = ${userSession.did} 89 + AND created_at > NOW() - INTERVAL '5 seconds' 90 + ORDER BY created_at DESC 91 + LIMIT 1 92 + `; 93 + 94 + if ((recentUpload as any[]).length > 0) { 95 + console.log(`User ${userSession.did} already saved within 5 seconds, skipping duplicate`); 96 + return { 97 + statusCode: 200, 98 + headers: { 'Content-Type': 'application/json' }, 99 + body: JSON.stringify({ success: true, message: 'Recently saved' }), 100 + }; 101 + } 83 102 84 103 // IMPORTANT: Create upload record FIRST before processing results 85 104 // This is required because user_source_follows has a foreign key to user_uploads
+21 -9
src/App.tsx
··· 1 - import { useState } from "react"; 1 + import { useState, useRef } from "react"; 2 2 import { ArrowRight } from "lucide-react"; 3 3 import LoginPage from "./pages/Login"; 4 4 import HomePage from "./pages/Home"; ··· 24 24 25 25 // Add state to track current platform 26 26 const [currentPlatform, setCurrentPlatform] = useState<string>('tiktok'); 27 + const saveCalledRef = useRef(false); 27 28 28 29 // Search hook 29 30 const { ··· 66 67 setStatusMessage, 67 68 () => { 68 69 setCurrentStep('results'); 69 - setSearchResults(currentResults => { 70 - const uploadId = crypto.randomUUID(); 71 - apiClient.saveResults(uploadId, platform, currentResults).catch(err => { 72 - console.error('Background save failed:', err); 73 - }); 74 - return currentResults; 75 - }); 70 + // Prevent duplicate saves 71 + if (!saveCalledRef.current) { 72 + saveCalledRef.current = true; 73 + // Need to wait for React to finish updating searchResults state 74 + // Use a longer delay and access via setSearchResults callback to get final state 75 + setTimeout(() => { 76 + setSearchResults(currentResults => { 77 + if (currentResults.length > 0) { 78 + const uploadId = crypto.randomUUID(); 79 + apiClient.saveResults(uploadId, platform, currentResults).catch(err => { 80 + console.error('Background save failed:', err); 81 + }); 82 + } 83 + return currentResults; // Don't modify, just return as-is 84 + }); 85 + }, 1000); // Longer delay to ensure all state updates complete 86 + } 76 87 } 77 88 ); 78 89 }, ··· 97 108 98 109 const platform = 'tiktok'; // Default, will be updated when we add platform to upload details 99 110 setCurrentPlatform(platform); 100 - 111 + saveCalledRef.current = false; 112 + 101 113 // Convert the loaded results to SearchResult format with selectedMatches 102 114 const loadedResults = data.results.map(result => ({ 103 115 ...result,