This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

add debugging

+176 -142
+131 -140
app/src/app/api/bluesky/feed/route.ts
··· 49 49 }); 50 50 51 51 export async function GET(request: NextRequest) { 52 + // Debug log all incoming requests with timestamp 53 + const requestTime = new Date().toISOString(); 54 + console.log(`\n=== FEED REQUEST @ ${requestTime} ===`); 55 + console.log(`URL: ${request.url}`); 56 + console.log(`Headers: ${JSON.stringify(Object.fromEntries(request.headers))}`); 57 + 52 58 try { 53 59 const now = Date.now(); 54 60 const url = new URL(request.url); 55 61 const forceRefresh = url.searchParams.get('refresh') === 'true'; 56 62 const beforeCursor = url.searchParams.get('before'); 63 + 64 + console.log(`Request params: forceRefresh=${forceRefresh}, beforeCursor=${beforeCursor || 'none'}`); 65 + console.log(`Current time: ${new Date(now).toISOString()}`); 66 + console.log(`Current cache age: ${now - lastFetchTime}ms, TTL: ${CACHE_TTL}ms`); 67 + console.log(`Cached entries count: ${cachedEntries.length}`); 68 + console.log(`DID resolution cache size: ${didResolutionCache.size}`); 69 + console.log(`DB handle cache size: ${dbHandleCache.size}`); 70 + console.log('=== END REQUEST INFO ==='); 57 71 58 72 // If we have a 'before' cursor, we're paginating and shouldn't use the cache 59 73 if (beforeCursor) { ··· 134 148 try { 135 149 if (supabaseUrl && supabaseKey) { 136 150 const supabase = createClient(supabaseUrl, supabaseKey); 137 - await supabase 151 + // Update all records with this DID to have the correct handle 152 + const { error: updateError } = await supabase 138 153 .from('flushing_records') 139 154 .update({ handle: authorHandle }) 140 155 .eq('did', authorDid); 141 - console.log(`Updated database with resolved handle for ${authorDid}: ${authorHandle}`); 156 + 157 + if (updateError) { 158 + console.error(`Error updating handle in DB: ${updateError.message}`); 159 + } else { 160 + console.log(`✅ Updated database with resolved handle for ${authorDid}: ${authorHandle}`); 161 + 162 + // For DEBUG - check if our update worked 163 + const { data: dbData } = await supabase 164 + .from('flushing_records') 165 + .select('id, did, handle, text, created_at') 166 + .eq('did', authorDid) 167 + .limit(1); 168 + 169 + console.log(`Current DB data for ${authorDid} after update:`, dbData); 170 + } 142 171 } 143 172 } catch (updateError) { 144 173 console.error(`Failed to update handle in database for ${authorDid}:`, updateError); ··· 174 203 } 175 204 } 176 205 177 - // For normal (non-pagination) requests, use the cache if valid and not forcing refresh 178 - if (!forceRefresh && now - lastFetchTime < CACHE_TTL && cachedEntries.length > 0) { 206 + // IMPORTANT: We're disabling the cache completely to ensure we always get fresh data 207 + // This is because we're having issues with stale data 208 + if (false && !forceRefresh && now - lastFetchTime < CACHE_TTL && cachedEntries.length > 0) { 179 209 console.log('Returning cached entries'); 180 210 return NextResponse.json({ entries: cachedEntries }); 181 211 } 182 212 183 - // If force refresh is requested, clear the didResolutionCache to force fresh handle resolution 184 - if (forceRefresh) { 185 - console.log('Force refresh requested, clearing DID resolution cache'); 186 - didResolutionCache.clear(); 187 - } 213 + // Clear the DID resolution cache on every request to ensure fresh resolution 214 + console.log('Clearing DID resolution cache to force fresh handle resolution'); 215 + didResolutionCache.clear(); 188 216 189 217 console.log('Fetching fresh entries'); 190 218 ··· 194 222 195 223 // Fetch entries from the flushing_records table 196 224 console.log(`Querying database for latest ${MAX_ENTRIES} entries...`); 225 + 226 + // Debug log the SQL query we're about to execute 227 + console.log('SQL Query: SELECT id, uri, cid, did, text, emoji, created_at, handle FROM flushing_records ORDER BY created_at DESC LIMIT 20'); 228 + 197 229 const { data: entries, error } = await supabase 198 230 .from('flushing_records') 199 231 .select(` ··· 213 245 throw new Error(`Supabase error: ${error.message}`); 214 246 } 215 247 216 - console.log(`Retrieved ${entries?.length || 0} entries from database. Latest created_at: ${entries?.[0]?.created_at || 'none'}`); 248 + console.log(`Retrieved ${entries?.length || 0} entries from database.`); 217 249 218 - // If no entries were found, this is suspicious - log a warning 219 - if (!entries || entries.length === 0) { 250 + // If entries found, log the most recent ones for debugging 251 + if (entries && entries.length > 0) { 252 + console.log('Latest entry:', { 253 + id: entries[0].id, 254 + did: entries[0].did, 255 + handle: entries[0].handle, 256 + text: entries[0].text.substring(0, 30) + (entries[0].text.length > 30 ? '...' : ''), 257 + created_at: entries[0].created_at 258 + }); 259 + 260 + // Debug: log the 5 most recent entries 261 + console.log('Recent entries:'); 262 + for (let i = 0; i < Math.min(5, entries.length); i++) { 263 + console.log(` ${i+1}. [${entries[i].id}] ${entries[i].did.substring(0, 20)}... - "${entries[i].text.substring(0, 20)}..." (${entries[i].created_at})`); 264 + } 265 + } else { 220 266 console.warn('No entries found in database - this may indicate a problem'); 221 267 } 222 268 ··· 377 423 }); 378 424 } 379 425 380 - // Function to attempt to resolve a DID to a handle with timeout 381 - // First tries PLC directory, then tries Bluesky API as a fallback 426 + // Direct fetch from PLC directory to resolve a DID to a handle 427 + // This is a simplified implementation that tries to be as reliable as possible 382 428 async function resolveDidToHandle(did: string): Promise<string> { 383 429 try { 384 - // Create a fallback shortened DID to use if all else fails 385 - const shortDid = did.startsWith('did:plc:') ? 386 - `${did.substring(0, 13)}...` : 387 - `${did.substring(0, 10)}...`; 388 - 389 430 // Check if we have a cached result for this DID 390 431 if (didResolutionCache.has(did)) { 391 432 return didResolutionCache.get(did)!; 392 433 } 393 434 394 - // Try PLC directory first - most reliable and doesn't require auth 435 + console.log(`Resolving handle for DID: ${did}`); 436 + 437 + // Create a fallback in case resolution fails 438 + const fallbackHandle = did.startsWith('did:plc:') ? 439 + did.substring(8, 20) : 440 + did.substring(0, 12); 441 + 442 + // Only try PLC directory for did:plc DIDs 395 443 if (did && did.startsWith('did:plc:')) { 396 444 try { 397 - // Use timeout to prevent hanging 398 - const plcResolver = async () => { 399 - console.log(`Trying PLC directory for DID: ${did}`); 445 + // Fetch directly from PLC directory with a simple GET request 446 + const url = `https://plc.directory/${did}`; 447 + console.log(`Fetching from ${url}`); 448 + 449 + const plcResponse = await fetch(url, { 450 + method: 'GET', 451 + // No signal/timeout here to ensure we get a response 452 + }); 453 + 454 + if (plcResponse.ok) { 455 + const plcData = await plcResponse.json(); 400 456 401 - // Function to extract handle from PLC data 402 - const extractHandleFromPlcData = (plcData: any): string | null => { 403 - if (!plcData || !plcData.alsoKnownAs || !Array.isArray(plcData.alsoKnownAs)) { 404 - return null; 405 - } 406 - 407 - // Loop through alsoKnownAs and find entries starting with "at://" 457 + // Debug: Log the entire response for diagnosis 458 + console.log(`Full PLC data for ${did}:`, JSON.stringify(plcData)); 459 + 460 + // Extract handle from alsoKnownAs if it exists 461 + if (plcData && plcData.alsoKnownAs && Array.isArray(plcData.alsoKnownAs)) { 462 + // Find the first entry that starts with "at://" 408 463 for (const aka of plcData.alsoKnownAs) { 409 464 if (typeof aka === 'string' && aka.startsWith('at://')) { 410 - // Extract the handle (everything after "at://") 411 - const handle = aka.split('//')[1]; 412 - if (handle) { 465 + // Extract the handle portion (after "at://") 466 + const handle = aka.substring(5); // "at://".length === 5 467 + 468 + if (handle && handle.length > 0) { 469 + console.log(`✅ Successfully resolved ${did} to handle: ${handle}`); 470 + 471 + // Cache it for future use 472 + didResolutionCache.set(did, handle); 473 + 474 + // Return the resolved handle 413 475 return handle; 414 476 } 415 477 } 416 478 } 417 - 418 - return null; 419 - }; 420 - 421 - try { 422 - // Try main PLC endpoint 423 - console.log(`Fetching from https://plc.directory/${did}`); 424 - const controller = new AbortController(); 425 - const timeoutId = setTimeout(() => controller.abort(), 3000); 426 - const plcResponse = await fetch(`https://plc.directory/${did}`, { 427 - signal: controller.signal 428 - }); 429 - clearTimeout(timeoutId); 430 - 431 - if (plcResponse.ok) { 432 - const plcData = await plcResponse.json(); 433 - console.log(`PLC response for ${did}:`, JSON.stringify(plcData).substring(0, 200) + '...'); 434 - 435 - const handle = extractHandleFromPlcData(plcData); 436 - if (handle) { 437 - console.log(`Successfully resolved ${did} to handle ${handle} via PLC directory`); 438 - didResolutionCache.set(did, handle); 439 - return handle; 440 - } else { 441 - console.log(`PLC data for ${did} did not contain a valid handle in alsoKnownAs:`, plcData.alsoKnownAs); 442 - } 443 - } else { 444 - console.log(`PLC response not OK: ${plcResponse.status} ${plcResponse.statusText}`); 445 - } 446 - } catch (err) { 447 - console.warn(`Error with main PLC endpoint for ${did}:`, err); 448 479 } 449 480 450 - try { 451 - // Try alternate PLC endpoint 452 - console.log(`Fetching from https://plc.directory/${did}/data`); 453 - const altController = new AbortController(); 454 - const altTimeoutId = setTimeout(() => altController.abort(), 3000); 455 - const altPlcResponse = await fetch(`https://plc.directory/${did}/data`, { 456 - signal: altController.signal 457 - }); 458 - clearTimeout(altTimeoutId); 459 - 460 - if (altPlcResponse.ok) { 461 - const altPlcData = await altPlcResponse.json(); 462 - console.log(`PLC alternate response for ${did}:`, JSON.stringify(altPlcData).substring(0, 200) + '...'); 463 - 464 - const handle = extractHandleFromPlcData(altPlcData); 465 - if (handle) { 466 - console.log(`Successfully resolved ${did} to handle ${handle} via PLC directory (alternate endpoint)`); 467 - didResolutionCache.set(did, handle); 468 - return handle; 469 - } else { 470 - console.log(`PLC alternate data for ${did} did not contain a valid handle in alsoKnownAs:`, altPlcData.alsoKnownAs); 471 - } 472 - } else { 473 - console.log(`PLC alternate response not OK: ${altPlcResponse.status} ${altPlcResponse.statusText}`); 474 - } 475 - } catch (err) { 476 - console.warn(`Error with alternate PLC endpoint for ${did}:`, err); 477 - } 478 - 479 - throw new Error('PLC resolution failed'); 480 - }; 481 - 482 - // Try PLC with a timeout 483 - await Promise.race([ 484 - plcResolver(), 485 - timeout(5000) // 5 second overall timeout 486 - ]); 487 - } catch (plcError) { 488 - console.warn(`Failed to resolve handle from PLC directory for DID ${did}:`, plcError); 489 - // Continue to next method 481 + console.warn(`❌ Could not find handle in PLC data for ${did}`); 482 + } else { 483 + console.warn(`❌ PLC fetch failed: ${plcResponse.status} ${plcResponse.statusText}`); 484 + } 485 + } catch (error) { 486 + console.error(`❌ Error fetching from PLC directory:`, error); 490 487 } 491 488 } 492 489 493 490 // Fall back to Bluesky API 494 491 try { 495 - console.log(`Falling back to Bluesky API for DID: ${did}`); 492 + console.log(`Trying Bluesky API for DID: ${did}`); 496 493 497 - const bskyResolver = async () => { 498 - // Try to resolve DID directly with Bluesky API 499 - await agent.login({ identifier: '', password: '' }); 500 - const response = await agent.getProfile({ actor: did }); 501 - if (response?.data?.handle) { 502 - console.log(`Resolved ${did} to handle ${response.data.handle} via Bluesky API`); 503 - didResolutionCache.set(did, response.data.handle); 504 - return response.data.handle; 505 - } 506 - throw new Error('Bluesky API resolution failed'); 507 - }; 494 + // Create a new agent for this request 495 + const agent = new BskyAgent({ 496 + service: 'https://bsky.social' 497 + }); 508 498 509 - // Try Bluesky API with a timeout 510 - await Promise.race([ 511 - bskyResolver(), 512 - timeout(5000) // 5 second timeout 513 - ]); 499 + // Log in with empty credentials (still required by the API) 500 + await agent.login({ identifier: '', password: '' }); 501 + 502 + // Get the profile 503 + const response = await agent.getProfile({ actor: did }); 504 + 505 + if (response && response.success && response.data && response.data.handle) { 506 + const handle = response.data.handle; 507 + console.log(`✅ Successfully resolved ${did} to handle via Bluesky API: ${handle}`); 508 + 509 + // Cache it for future use 510 + didResolutionCache.set(did, handle); 511 + 512 + // Return the resolved handle 513 + return handle; 514 + } else { 515 + console.warn(`❌ Bluesky API resolution failed for ${did}`); 516 + } 514 517 } catch (apiError) { 515 - console.error(`Failed to resolve handle with Bluesky API for DID ${did}:`, apiError); 518 + console.error(`❌ Error with Bluesky API:`, apiError); 516 519 } 517 520 518 - // If we get here, all resolution methods failed 519 - console.log(`All resolution methods failed for ${did}, using shortened DID: ${shortDid}`); 520 - 521 - // Create a user-friendly version of the DID 522 - // Format: "user.{first6chars}" 523 - const userFriendlyDid = did.startsWith('did:plc:') ? 524 - `user.${did.substring(8, 14)}` : 525 - `user.${did.substring(0, 6)}`; 526 - 527 - // Cache the fallback result too 528 - didResolutionCache.set(did, userFriendlyDid); 529 - return userFriendlyDid; 521 + // If all resolution methods failed, use the fallback 522 + console.log(`❌ All resolution methods failed for ${did}, using fallback: ${fallbackHandle}`); 523 + didResolutionCache.set(did, fallbackHandle); 524 + return fallbackHandle; 530 525 } catch (error) { 531 - console.error(`Failed to resolve handle for DID ${did}:`, error); 532 - // Last resort fallback is a user-friendly version of the DID 533 - const userFriendlyDid = did.startsWith('did:plc:') ? 534 - `user.${did.substring(8, 14)}` : 535 - `user.${did.substring(0, 6)}`; 536 - return userFriendlyDid; 526 + console.error(`❌ Unexpected error resolving handle for ${did}:`, error); 527 + return did.substring(0, 12); // Last resort fallback 537 528 } 538 529 }
+45 -2
app/src/app/page.tsx
··· 211 211 ? '/api/bluesky/feed?refresh=true' 212 212 : '/api/bluesky/feed'; 213 213 214 + console.log(`Fetching feed from ${url} at ${new Date().toISOString()}`); 215 + 214 216 const response = await fetch(url, { 215 217 cache: 'no-store', 216 218 headers: { ··· 224 226 } 225 227 226 228 const data = await response.json(); 229 + console.log(`Received ${data.entries?.length || 0} entries from API`); 230 + 231 + // Debug: Log the most recent entries we received 232 + if (data.entries && data.entries.length > 0) { 233 + console.log('Latest entries from API:'); 234 + for (let i = 0; i < Math.min(3, data.entries.length); i++) { 235 + const entry = data.entries[i]; 236 + console.log(` ${i+1}. ID: ${entry.id}, Handle: @${entry.authorHandle}, Text: "${entry.text.substring(0, 20)}..."`); 237 + } 238 + } 227 239 228 240 // Check for new entries 229 241 if (entries.length > 0) { 230 242 const currentIds = new Set(entries.map((entry: FlushingEntry) => entry.id)); 231 243 const newEntries = data.entries.filter((entry: FlushingEntry) => !currentIds.has(entry.id)); 232 244 233 - // Mark new entries for animation 245 + // Log new entries 234 246 if (newEntries.length > 0) { 247 + console.log(`Found ${newEntries.length} new entries`); 248 + 249 + // Mark new entries for animation 235 250 setNewEntryIds(new Set(newEntries.map((entry: FlushingEntry) => entry.id))); 236 251 237 252 // Clear the animation markers after animation completes 238 253 setTimeout(() => { 239 254 setNewEntryIds(new Set()); 240 255 }, 2000); 256 + } else { 257 + console.log('No new entries found in this update'); 241 258 } 242 259 } 243 260 ··· 431 448 </p> 432 449 </div> 433 450 <button 434 - onClick={() => fetchLatestEntries(true)} 451 + onClick={() => { 452 + // Guaranteed fresh data with cache busting 453 + setLoading(true); 454 + const uniqueUrl = `/api/bluesky/feed?refresh=true&_t=${Date.now()}`; 455 + console.log(`Manual refresh with unique URL: ${uniqueUrl}`); 456 + fetch(uniqueUrl, { 457 + cache: 'no-store', 458 + headers: { 459 + 'Cache-Control': 'no-cache, no-store, must-revalidate', 460 + 'Pragma': 'no-cache', 461 + 'Expires': '0' 462 + } 463 + }) 464 + .then(response => response.json()) 465 + .then(data => { 466 + console.log(`Manual refresh got ${data.entries?.length || 0} entries`); 467 + if (data.entries?.length > 0) { 468 + console.log(`First entry ID: ${data.entries[0].id}, text: ${data.entries[0].text.substring(0, 20)}...`); 469 + } 470 + setEntries(data.entries || []); 471 + setLoading(false); 472 + }) 473 + .catch(err => { 474 + console.error('Manual refresh error:', err); 475 + setLoading(false); 476 + }); 477 + }} 435 478 className={styles.refreshButton} 436 479 disabled={loading} 437 480 >