···
16
16
'1️⃣', '2️⃣', '🟡', '🟤'
17
17
];
18
18
19
19
+
// Function to fetch true count from Bluesky API for a specific DID
20
20
+
async function fetchTrueCountFromBluesky(did: string): Promise<number> {
21
21
+
try {
22
22
+
console.log(`Fetching true count for DID: ${did}`);
23
23
+
24
24
+
// Step 1: Get PDS endpoint from PLC directory
25
25
+
const plcResponse = await fetch(`https://plc.directory/${did}/data`);
26
26
+
27
27
+
if (!plcResponse.ok) {
28
28
+
console.warn(`Failed to get PLC data for ${did}: ${plcResponse.statusText}`);
29
29
+
return 0;
30
30
+
}
31
31
+
32
32
+
const plcData = await plcResponse.json();
33
33
+
const pdsService = plcData.services?.atproto_pds;
34
34
+
35
35
+
if (!pdsService?.endpoint) {
36
36
+
console.warn(`No PDS service endpoint found for ${did}`);
37
37
+
return 0;
38
38
+
}
39
39
+
40
40
+
// Extract the hostname from the PDS endpoint
41
41
+
const serviceUrl = new URL(pdsService.endpoint);
42
42
+
const servicePds = serviceUrl.hostname;
43
43
+
const serviceEndpoint = `https://${servicePds}`;
44
44
+
45
45
+
console.log(`Using PDS endpoint for ${did}: ${serviceEndpoint}`);
46
46
+
47
47
+
// Step 2: Fetch all records from PDS
48
48
+
let allRecords: any[] = [];
49
49
+
let cursor: string | undefined;
50
50
+
let hasMore = true;
51
51
+
let pageCount = 0;
52
52
+
const maxPages = 100; // Safety limit to prevent infinite loops
53
53
+
54
54
+
while (hasMore && pageCount < maxPages) {
55
55
+
const listRecordsUrl = `${serviceEndpoint}/xrpc/com.atproto.repo.listRecords?repo=${encodeURIComponent(did)}&collection=im.flushing.right.now&limit=100${cursor ? `&cursor=${encodeURIComponent(cursor)}` : ''}`;
56
56
+
57
57
+
try {
58
58
+
const recordsResponse = await fetch(listRecordsUrl, {
59
59
+
headers: {
60
60
+
'Accept': 'application/json'
61
61
+
},
62
62
+
// Add timeout to prevent hanging
63
63
+
signal: AbortSignal.timeout(5000)
64
64
+
});
65
65
+
66
66
+
if (!recordsResponse.ok) {
67
67
+
// If we get a 404, the collection might be empty
68
68
+
if (recordsResponse.status === 404) {
69
69
+
console.log(`Empty collection for ${did}`);
70
70
+
break;
71
71
+
}
72
72
+
73
73
+
console.warn(`Failed to fetch records for ${did}: ${recordsResponse.statusText}`);
74
74
+
break;
75
75
+
}
76
76
+
77
77
+
const recordsData = await recordsResponse.json();
78
78
+
allRecords = [...allRecords, ...recordsData.records];
79
79
+
80
80
+
cursor = recordsData.cursor;
81
81
+
hasMore = !!cursor && recordsData.records.length === 100;
82
82
+
pageCount++;
83
83
+
84
84
+
console.log(`Fetched page ${pageCount} for ${did}: ${recordsData.records.length} records, total: ${allRecords.length}`);
85
85
+
86
86
+
} catch (fetchError) {
87
87
+
console.error(`Error fetching records for ${did}:`, fetchError);
88
88
+
break;
89
89
+
}
90
90
+
}
91
91
+
92
92
+
if (pageCount >= maxPages) {
93
93
+
console.warn(`Reached maximum page limit for ${did}, may have incomplete data`);
94
94
+
}
95
95
+
96
96
+
console.log(`Total records fetched for ${did}: ${allRecords.length}`);
97
97
+
return allRecords.length;
98
98
+
99
99
+
} catch (error) {
100
100
+
console.error(`Error fetching true count for ${did}:`, error);
101
101
+
return 0;
102
102
+
}
103
103
+
}
104
104
+
19
105
export async function GET(request: NextRequest) {
20
106
try {
21
107
// Define the plumber's DID - this is the official plumber account DID
22
22
-
const PLUMBER_DID = 'did:plc:fouf3svmcxzn6bpiw3lgwz22';
108
108
+
const PLUMBER_DID = 'did:plc:fouf3svmcxzn6bpiw6iyxxg2o3rljw';
23
109
24
110
// List of DIDs to exclude from leaderboard
25
111
const excludedDids = [
26
112
PLUMBER_DID, // plumber.flushes.app (formerly plumber.flushing.im)
27
27
-
'did:plc:fnhrjbkwjiw6iyxxg2o3rljw' // testing.dame.is
113
113
+
'did:plc:fouf3svmcxzn6bpiw3lgwz22' // testing.dame.is
28
114
];
29
115
30
116
// List of handles to exclude from leaderboard (as fallback)
···
241
327
throw new Error(`Failed to get leaderboard data: ${leaderboardError.message}`);
242
328
}
243
329
244
244
-
// Count flushes by DID
330
330
+
// Count flushes by DID from Supabase (for ranking only)
245
331
const didCounts = new Map<string, number>();
246
332
247
333
// Special count for the plumber account
···
261
347
}
262
348
});
263
349
264
264
-
// Convert to array and sort by count
265
265
-
const leaderboard = Array.from(didCounts.entries())
266
266
-
.map(([did, count]): {did: string, count: number} => ({ did, count }))
267
267
-
.sort((a, b) => b.count - a.count)
268
268
-
.slice(0, 10); // Get top 10
350
350
+
// Convert to array and sort by count to get top 10 DIDs
351
351
+
const top10Dids = Array.from(didCounts.entries())
352
352
+
.sort((a, b) => b[1] - a[1])
353
353
+
.slice(0, 10)
354
354
+
.map(([did]) => did);
355
355
+
356
356
+
console.log(`Top 10 DIDs from Supabase ranking: ${top10Dids.join(', ')}`);
357
357
+
358
358
+
// Now fetch true counts from Bluesky API for these top 10 DIDs
359
359
+
console.log('Fetching true counts from Bluesky API for top 10 users...');
360
360
+
const leaderboardWithTrueCounts = await Promise.all(
361
361
+
top10Dids.map(async (did) => {
362
362
+
const trueCount = await fetchTrueCountFromBluesky(did);
363
363
+
const supabaseCount = didCounts.get(did) || 0;
364
364
+
365
365
+
console.log(`DID ${did}: Supabase count = ${supabaseCount}, True count = ${trueCount}`);
366
366
+
367
367
+
return {
368
368
+
did,
369
369
+
count: trueCount, // Use the true count from Bluesky API
370
370
+
supabaseCount // Include for comparison/debugging
371
371
+
};
372
372
+
})
373
373
+
);
374
374
+
375
375
+
// Sort by true count (descending) in case the order changed
376
376
+
const leaderboard = leaderboardWithTrueCounts
377
377
+
.sort((a, b) => b.count - a.count);
269
378
270
379
// Calculate total unique flushers (count of unique DIDs)
271
380
const totalFlushers = didCounts.size;
···
10
10
totalCount: number;
11
11
flushesPerDay: number;
12
12
chartData: { date: string; count: number }[];
13
13
-
leaderboard: { did: string; count: number; handle?: string }[];
13
13
+
leaderboard: { did: string; count: number; handle?: string; supabaseCount?: number }[];
14
14
plumberFlushCount: number;
15
15
totalFlushers: number;
16
16
monthlyActiveFlushers: number;
···
211
211
{/* Leaderboard */}
212
212
<section className={styles.leaderboardSection}>
213
213
<h2>Top Flushers</h2>
214
214
+
<p className={styles.leaderboardNote}>
215
215
+
📊 Counts are fetched directly from Bluesky API for accuracy
216
216
+
</p>
214
217
{statsData.leaderboard.length > 0 ? (
215
218
<div className={styles.leaderboard}>
216
219
<div className={styles.leaderboardHeader}>
···
233
233
text-align: left;
234
234
}
235
235
236
236
+
.leaderboardNote {
237
237
+
color: var(--timestamp-color);
238
238
+
font-size: 0.9rem;
239
239
+
margin-top: -1rem;
240
240
+
margin-bottom: 1.5rem;
241
241
+
text-align: center;
242
242
+
font-style: italic;
243
243
+
}
244
244
+
236
245
.plumberProfileLink {
237
246
display: inline-block;
238
247
color: var(--primary-color);