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